Skip to content

Commit 3cacb55

Browse files
build: emit LLVM objects directly from modules
- replace the .ll -> clang -c path for LLGo-generated modules with in-process LLVM object emission - route translated plan9 asm modules and link-time .ll inputs through the same LLVM emission helper - map existing clang target/codegen flags onto LLVM target machine and pass pipeline settings - erase replaced cabi call instructions after rewriting uses to avoid leaving dead instructions behind Testing: - go test ./internal/build -run TestRun - go test ./ssa -run TestFromTestlibgo/atomic - go test ./internal/clang
1 parent 9fa934f commit 3cacb55

File tree

7 files changed

+322
-81
lines changed

7 files changed

+322
-81
lines changed

internal/build/build.go

Lines changed: 21 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -273,9 +273,14 @@ func Do(args []string, conf *Config) ([]Package, error) {
273273
llssa.Initialize(llssa.InitAll)
274274

275275
target := &llssa.Target{
276-
GOOS: conf.Goos,
277-
GOARCH: conf.Goarch,
278-
Target: conf.Target,
276+
GOOS: conf.Goos,
277+
GOARCH: conf.Goarch,
278+
Target: conf.Target,
279+
Triple: export.LLVMTarget,
280+
CPU: export.CPU,
281+
Features: export.Features,
282+
CodeModel: export.CodeModel,
283+
RelocationModel: export.RelocationModel,
279284
}
280285

281286
prog := llssa.NewProgram(target)
@@ -999,7 +1004,7 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, outputPa
9991004
methodByName: methodByName,
10001005
abiSymbols: linkedModuleGlobals(linkedOrder),
10011006
})
1002-
entryObjFile, err := exportObject(ctx, "entry_main", entryPkg.ExportFile, []byte(entryPkg.LPkg.String()))
1007+
entryObjFile, err := exportObject(ctx, "entry_main", entryPkg.ExportFile, entryPkg.LPkg.Module())
10031008
if err != nil {
10041009
return err
10051010
}
@@ -1088,11 +1093,10 @@ func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose
10881093
for _, objFile := range objFiles {
10891094
if strings.HasSuffix(objFile, ".ll") {
10901095
oFile := strings.TrimSuffix(objFile, ".ll") + ".o"
1091-
args := []string{"-o", oFile, "-c", objFile, "-Wno-override-module"}
10921096
if printCmds {
1093-
fmt.Fprintln(os.Stderr, "clang", args)
1097+
fmt.Fprintf(os.Stderr, "# compiling %s from IR %s\n", oFile, objFile)
10941098
}
1095-
if err := ctx.compiler().Compile(args...); err != nil {
1099+
if err := ctx.emitIRFileObject("link", objFile, oFile); err != nil {
10961100
return fmt.Errorf("failed to compile %s: %v", objFile, err)
10971101
}
10981102
compiledObjFiles = append(compiledObjFiles, oFile)
@@ -1292,7 +1296,7 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error {
12921296
aPkg.LinkArgs = append(aPkg.LinkArgs, goCgoLinkArgs(ctx.buildConf.Goos, aPkg.AltPkg.Syntax)...)
12931297
}
12941298
if pkg.ExportFile != "" {
1295-
exportFile, err := exportObject(ctx, pkg.PkgPath, pkg.ExportFile, []byte(ret.String()))
1299+
exportFile, err := exportObject(ctx, pkg.PkgPath, pkg.ExportFile, ret.Module())
12961300
if err != nil {
12971301
return fmt.Errorf("export object of %v failed: %v", pkgPath, err)
12981302
}
@@ -1304,49 +1308,26 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error {
13041308
return nil
13051309
}
13061310

1307-
func exportObject(ctx *context, pkgPath string, exportFile string, data []byte) (string, error) {
1311+
func exportObject(ctx *context, pkgPath string, exportFile string, mod gllvm.Module) (string, error) {
13081312
base := filepath.Base(exportFile)
1309-
f, err := os.CreateTemp("", base+"-*.ll")
1310-
if err != nil {
1311-
return "", err
1312-
}
1313-
if _, err := f.Write(data); err != nil {
1314-
f.Close()
1315-
return "", err
1316-
}
1317-
err = f.Close()
1318-
if err != nil {
1319-
return exportFile, err
1320-
}
1321-
if ctx.buildConf.CheckLLFiles {
1322-
if msg, err := llcCheck(ctx.env, f.Name()); err != nil {
1323-
fmt.Fprintf(os.Stderr, "==> lcc %v: %v\n%v\n", pkgPath, f.Name(), msg)
1324-
}
1325-
}
1326-
// If GenLL is enabled, keep a copy of the .ll file for debugging
1327-
if ctx.buildConf.GenLL {
1328-
llFile := exportFile + ".ll"
1329-
if err := os.Chmod(f.Name(), 0644); err != nil {
1330-
return "", err
1331-
}
1332-
// Copy instead of rename so we can still compile to .o
1333-
if err := copyFileAtomic(f.Name(), llFile); err != nil {
1313+
if ctx.buildConf.GenLL || ctx.buildConf.CheckLLFiles {
1314+
if err := ctx.writeModuleIR(base, exportFile, mod); err != nil {
13341315
return "", err
13351316
}
13361317
}
1337-
// Always compile .ll to .o for linking
13381318
objFile, err := os.CreateTemp("", base+"-*.o")
13391319
if err != nil {
13401320
return "", err
13411321
}
13421322
objFile.Close()
1343-
args := []string{"-o", objFile.Name(), "-c", f.Name(), "-Wno-override-module"}
13441323
if ctx.shouldPrintCommands(false) {
1345-
fmt.Fprintf(os.Stderr, "# compiling %s for pkg: %s\n", f.Name(), pkgPath)
1346-
fmt.Fprintln(os.Stderr, "clang", args)
1324+
fmt.Fprintf(os.Stderr, "# emitting %s for pkg: %s via LLVM\n", objFile.Name(), pkgPath)
13471325
}
1348-
cmd := ctx.compiler()
1349-
return objFile.Name(), cmd.Compile(args...)
1326+
if err := ctx.emitModuleObject(pkgPath, mod, objFile.Name()); err != nil {
1327+
os.Remove(objFile.Name())
1328+
return "", err
1329+
}
1330+
return objFile.Name(), nil
13501331
}
13511332

13521333
func llcCheck(env *llvm.Env, exportFile string) (msg string, err error) {

internal/build/llvm_emit.go

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
package build
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
9+
llssa "github.com/goplus/llgo/ssa"
10+
gllvm "github.com/goplus/llvm"
11+
)
12+
13+
type llvmCompileConfig struct {
14+
target llssa.Target
15+
passPipeline string
16+
}
17+
18+
func (c *context) llvmCompileConfig() llvmCompileConfig {
19+
cfg := llvmCompileConfig{}
20+
if base := c.prog.Target(); base != nil {
21+
cfg.target = *base
22+
}
23+
if cfg.target.Triple == "" && c.crossCompile.LLVMTarget != "" {
24+
cfg.target.Triple = c.crossCompile.LLVMTarget
25+
}
26+
if cfg.target.CPU == "" && c.crossCompile.CPU != "" {
27+
cfg.target.CPU = c.crossCompile.CPU
28+
}
29+
if cfg.target.Features == "" && c.crossCompile.Features != "" {
30+
cfg.target.Features = c.crossCompile.Features
31+
}
32+
if cfg.target.CodeModel == "" && c.crossCompile.CodeModel != "" {
33+
cfg.target.CodeModel = c.crossCompile.CodeModel
34+
}
35+
if cfg.target.RelocationModel == "" && c.crossCompile.RelocationModel != "" {
36+
cfg.target.RelocationModel = c.crossCompile.RelocationModel
37+
}
38+
39+
flags := c.compiler().CompilerFlags()
40+
parseLLVMCompileFlags(&cfg, flags)
41+
if cfg.target.RelocationModel == "" {
42+
cfg.target.RelocationModel = defaultLLVMRelocationModel(cfg.target)
43+
}
44+
return cfg
45+
}
46+
47+
func defaultLLVMRelocationModel(target llssa.Target) string {
48+
switch target.GOOS {
49+
case "linux", "android", "darwin", "ios", "freebsd", "netbsd", "openbsd":
50+
if target.GOARCH != "wasm" {
51+
return "pic"
52+
}
53+
}
54+
return ""
55+
}
56+
57+
func parseLLVMCompileFlags(cfg *llvmCompileConfig, flags []string) {
58+
for i := 0; i < len(flags); i++ {
59+
arg := flags[i]
60+
switch {
61+
case arg == "-O0":
62+
cfg.passPipeline = "default<O0>"
63+
cfg.target.CodeGenLevel = gllvm.CodeGenLevelNone
64+
case arg == "-O1" || arg == "-O":
65+
cfg.passPipeline = "default<O1>"
66+
cfg.target.CodeGenLevel = gllvm.CodeGenLevelLess
67+
case arg == "-O2":
68+
cfg.passPipeline = "default<O2>"
69+
cfg.target.CodeGenLevel = gllvm.CodeGenLevelDefault
70+
case arg == "-O3":
71+
cfg.passPipeline = "default<O3>"
72+
cfg.target.CodeGenLevel = gllvm.CodeGenLevelAggressive
73+
case arg == "-Os":
74+
cfg.passPipeline = "default<Os>"
75+
cfg.target.CodeGenLevel = gllvm.CodeGenLevelDefault
76+
case arg == "-Oz":
77+
cfg.passPipeline = "default<Oz>"
78+
cfg.target.CodeGenLevel = gllvm.CodeGenLevelDefault
79+
case arg == "-target" || arg == "--target":
80+
if i+1 < len(flags) {
81+
i++
82+
cfg.target.Triple = flags[i]
83+
}
84+
case strings.HasPrefix(arg, "-target="):
85+
cfg.target.Triple = strings.TrimPrefix(arg, "-target=")
86+
case strings.HasPrefix(arg, "--target="):
87+
cfg.target.Triple = strings.TrimPrefix(arg, "--target=")
88+
case arg == "-mcpu" || arg == "-march" || arg == "-mmcu":
89+
if i+1 < len(flags) {
90+
i++
91+
cfg.target.CPU = flags[i]
92+
}
93+
case strings.HasPrefix(arg, "-mcpu="):
94+
cfg.target.CPU = strings.TrimPrefix(arg, "-mcpu=")
95+
case strings.HasPrefix(arg, "-march="):
96+
cfg.target.CPU = strings.TrimPrefix(arg, "-march=")
97+
case strings.HasPrefix(arg, "-mmcu="):
98+
cfg.target.CPU = strings.TrimPrefix(arg, "-mmcu=")
99+
case arg == "-mattr":
100+
if i+1 < len(flags) {
101+
i++
102+
cfg.target.Features = flags[i]
103+
}
104+
case strings.HasPrefix(arg, "-mattr="):
105+
cfg.target.Features = strings.TrimPrefix(arg, "-mattr=")
106+
case arg == "-mcmodel":
107+
if i+1 < len(flags) {
108+
i++
109+
cfg.target.CodeModel = flags[i]
110+
}
111+
case strings.HasPrefix(arg, "-mcmodel="):
112+
cfg.target.CodeModel = strings.TrimPrefix(arg, "-mcmodel=")
113+
case arg == "-fPIC":
114+
cfg.target.RelocationModel = "pic"
115+
case arg == "-fno-pic":
116+
cfg.target.RelocationModel = "static"
117+
case arg == "-mllvm":
118+
if i+1 < len(flags) {
119+
i++
120+
parseLLVMBackendFlag(cfg, flags[i])
121+
}
122+
case strings.HasPrefix(arg, "-mllvm="):
123+
parseLLVMBackendFlag(cfg, strings.TrimPrefix(arg, "-mllvm="))
124+
}
125+
}
126+
}
127+
128+
func parseLLVMBackendFlag(cfg *llvmCompileConfig, arg string) {
129+
switch {
130+
case strings.HasPrefix(arg, "-mcpu="):
131+
cfg.target.CPU = strings.TrimPrefix(arg, "-mcpu=")
132+
case strings.HasPrefix(arg, "-mattr="):
133+
cfg.target.Features = strings.TrimPrefix(arg, "-mattr=")
134+
}
135+
}
136+
137+
func (c *context) emitModuleObject(pkgPath string, mod gllvm.Module, objPath string) error {
138+
cfg := c.llvmCompileConfig()
139+
140+
mod.SetTarget(cfg.target.Spec().Triple)
141+
td, tm := cfg.target.CreateTargetMachine()
142+
defer td.Dispose()
143+
defer tm.Dispose()
144+
mod.SetDataLayout(td.String())
145+
if cfg.passPipeline != "" {
146+
pbo := gllvm.NewPassBuilderOptions()
147+
defer pbo.Dispose()
148+
if err := mod.RunPasses(cfg.passPipeline, tm, pbo); err != nil {
149+
return fmt.Errorf("%s: run LLVM passes %q: %w", pkgPath, cfg.passPipeline, err)
150+
}
151+
}
152+
153+
buf, err := tm.EmitToMemoryBuffer(mod, gllvm.ObjectFile)
154+
if err != nil {
155+
return fmt.Errorf("%s: emit object: %w", pkgPath, err)
156+
}
157+
defer buf.Dispose()
158+
159+
if err := os.WriteFile(objPath, buf.Bytes(), 0644); err != nil {
160+
return fmt.Errorf("%s: write object %s: %w", pkgPath, objPath, err)
161+
}
162+
return nil
163+
}
164+
165+
func (c *context) emitIRFileObject(pkgPath string, llPath string, objPath string) error {
166+
buf, err := gllvm.NewMemoryBufferFromFile(llPath)
167+
if err != nil {
168+
return fmt.Errorf("%s: open IR file %s: %w", pkgPath, llPath, err)
169+
}
170+
171+
ctx := gllvm.NewContext()
172+
defer ctx.Dispose()
173+
174+
mod, err := ctx.ParseIR(buf)
175+
if err != nil {
176+
return fmt.Errorf("%s: parse IR file %s: %w", pkgPath, llPath, err)
177+
}
178+
defer mod.Dispose()
179+
180+
return c.emitModuleObject(pkgPath, mod, objPath)
181+
}
182+
183+
func (c *context) writeModuleIR(base string, exportFile string, mod gllvm.Module) error {
184+
ll := mod.String()
185+
186+
tmp, err := os.CreateTemp("", base+"-*.ll")
187+
if err != nil {
188+
return err
189+
}
190+
tmpPath := tmp.Name()
191+
if _, err := tmp.WriteString(ll); err != nil {
192+
tmp.Close()
193+
os.Remove(tmpPath)
194+
return err
195+
}
196+
if err := tmp.Close(); err != nil {
197+
os.Remove(tmpPath)
198+
return err
199+
}
200+
defer os.Remove(tmpPath)
201+
202+
if c.buildConf.CheckLLFiles {
203+
if msg, err := llcCheck(c.env, tmpPath); err != nil {
204+
fmt.Fprintf(os.Stderr, "==> lcc %v: %v\n%v\n", filepath.Base(exportFile), tmpPath, msg)
205+
}
206+
}
207+
if c.buildConf.GenLL {
208+
llFile := exportFile + ".ll"
209+
if err := os.Chmod(tmpPath, 0644); err != nil {
210+
return err
211+
}
212+
if err := copyFileAtomic(tmpPath, llFile); err != nil {
213+
return err
214+
}
215+
}
216+
return nil
217+
}

internal/build/plan9asm.go

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -77,35 +77,13 @@ func compilePkgSFiles(ctx *context, aPkg *aPackage, pkg *packages.Package, verbo
7777
if pkg.PkgPath != "runtime" {
7878
ctx.cTransformer.TransformModule(pkg.PkgPath, mod)
7979
}
80-
ll := mod.String()
81-
mod.Dispose()
82-
8380
baseName := aPkg.ExportFile + filepath.Base(sfile) // used for stable debug output paths
8481
tmpPrefix := "plan9asm-" + filepath.Base(sfile) + "-"
8582

86-
llFile, err := os.CreateTemp("", tmpPrefix+"*.ll")
87-
if err != nil {
88-
return nil, fmt.Errorf("%s: create temp .ll for %s: %w", pkg.PkgPath, sfile, err)
89-
}
90-
llPath := llFile.Name()
91-
if _, err := llFile.WriteString(ll); err != nil {
92-
llFile.Close()
93-
os.Remove(llPath)
94-
return nil, fmt.Errorf("%s: write temp .ll for %s: %w", pkg.PkgPath, sfile, err)
95-
}
96-
if err := llFile.Close(); err != nil {
97-
os.Remove(llPath)
98-
return nil, fmt.Errorf("%s: close temp .ll for %s: %w", pkg.PkgPath, sfile, err)
99-
}
100-
defer os.Remove(llPath)
101-
10283
// Keep a copy of translated .ll when GenLL is enabled (mirrors clFile/exportObject).
103-
if ctx.buildConf.GenLL {
104-
dst := baseName + ".ll"
105-
if err := os.Chmod(llPath, 0644); err != nil {
106-
return nil, fmt.Errorf("%s: chmod temp .ll for %s: %w", pkg.PkgPath, sfile, err)
107-
}
108-
if err := copyFileAtomic(llPath, dst); err != nil {
84+
if ctx.buildConf.GenLL || ctx.buildConf.CheckLLFiles {
85+
if err := ctx.writeModuleIR(tmpPrefix, baseName, mod); err != nil {
86+
mod.Dispose()
10987
return nil, fmt.Errorf("%s: keep .ll for %s: %w", pkg.PkgPath, sfile, err)
11088
}
11189
}
@@ -117,15 +95,15 @@ func compilePkgSFiles(ctx *context, aPkg *aPackage, pkg *packages.Package, verbo
11795
objPath := objFile.Name()
11896
objFile.Close()
11997

120-
args := []string{"-o", objPath, "-c", llPath, "-Wno-override-module"}
12198
if ctx.shouldPrintCommands(verbose) {
122-
fmt.Fprintf(os.Stderr, "# compiling %s for pkg: %s\n", objPath, pkg.PkgPath)
123-
fmt.Fprintln(os.Stderr, "clang", args)
99+
fmt.Fprintf(os.Stderr, "# emitting %s for pkg: %s via LLVM\n", objPath, pkg.PkgPath)
124100
}
125-
if err := ctx.compiler().Compile(args...); err != nil {
101+
if err := ctx.emitModuleObject(pkg.PkgPath, mod, objPath); err != nil {
102+
mod.Dispose()
126103
os.Remove(objPath)
127-
return nil, fmt.Errorf("%s: clang compile asm ll for %s: %w", pkg.PkgPath, sfile, err)
104+
return nil, fmt.Errorf("%s: emit asm object for %s: %w", pkg.PkgPath, sfile, err)
128105
}
106+
mod.Dispose()
129107
objFiles = append(objFiles, objPath)
130108
}
131109

internal/cabi/cabi.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,7 @@ func (p *Transformer) transformCallInstr(m llvm.Module, ctx llvm.Context, call l
617617
updateCallAttr(instr)
618618
}
619619
call.ReplaceAllUsesWith(instr)
620-
call.RemoveFromParentAsInstruction()
620+
call.EraseFromParentAsInstruction()
621621
return true
622622
}
623623

0 commit comments

Comments
 (0)