Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/cmd/compile/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ Flags:
object to usual output file (as specified by -o).
Without this flag, the -o output is a combination of both
linker and compiler input.
-llvm-lto
With the LLVM backend, emit package bitcode for FullLTO instead of
machine code. Required early IR lowering and LLVM's lto-pre-link<O2>
pipeline run before serialization; whole-program optimization and
code generation remain link-time work. With -llvm-keep-ir, .ll shows
prepared IR and .opt.ll shows the pre-link optimized IR in the archive.
-m
Print optimization decisions. Higher values or repetition
produce more detail.
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/base/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ type CmdFlags struct {
PgoProfile string "help:\"read profile or pre-process profile from `file`\""
ErrorURL bool "help:\"print explanatory URL with error message if applicable\""
EnableLLVM bool "help:\"compile with the in-process LLVM backend\""
LLVMLTO bool `flag:"llvm-lto" help:"emit LLVM bitcode for LTO at link time"`
LLVMLTO bool `flag:"llvm-lto" help:"emit pre-link optimized LLVM bitcode for LTO at link time"`
LLVMKeepIR bool "flag:\"llvm-keep-ir\" help:\"keep frontend (.raw.ll), prepared (.ll), and optimized (.opt.ll) LLVM IR beside the output archive\""
LLVMOptPasses string "flag:\"llvm-opt-passes\" help:\"LLVM optimization pipeline (auto selects O0 with -N, O2 otherwise)\""
// Configuration derived from flags; not a flag itself.
Expand Down
40 changes: 36 additions & 4 deletions src/cmd/compile/internal/ssa/llvm_lto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"internal/testenv"
"os"
"path/filepath"
"regexp"
"strings"
"testing"

Expand All @@ -24,11 +25,11 @@ func TestLLVMSlimArchive(t *testing.T) {
t.Run(arch, func(t *testing.T) {
dir := t.TempDir()
source := filepath.Join(dir, "p.go")
if err := os.WriteFile(source, []byte("package p\nfunc F(x int) int { return x + 1 }\n"), 0600); err != nil {
if err := os.WriteFile(source, []byte("package p\nfunc constant(x int) int { return 42 }\nfunc F(x int) int { return constant(x) }\n"), 0600); err != nil {
t.Fatal(err)
}
output := filepath.Join(dir, "p.a")
cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "compile", "-enablellvm", "-llvm-lto", "-llvm-keep-ir", "-p=p", "-o", output, source)
cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "compile", "-enablellvm", "-llvm-lto", "-llvm-keep-ir", "-l", "-p=p", "-o", output, source)
cmd.Env = append(cmd.Environ(), "GOOS=linux", "GOARCH="+arch)
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("compile: %v\n%s", err, out)
Expand Down Expand Up @@ -80,8 +81,39 @@ func TestLLVMSlimArchive(t *testing.T) {
if !bytes.Contains(ir, []byte("!goobj.config")) {
t.Fatal("pre-optimization IR lost GoObj configuration")
}
if _, err := os.Stat(output + ".opt.ll"); !os.IsNotExist(err) {
t.Fatalf("LTO unexpectedly produced optimized IR: %v", err)
// Disable inlining above so this pure constant-returning call can only
// disappear through LLVM constant propagation and dead-call elimination in
// the package-level pipeline, before the archive is linked.
if !bytes.Contains(ir, []byte("call goabiinternal i64 @p.constant(")) {
t.Fatal("prepared IR has no call to exercise pre-link constant propagation")
}
if body := module.NamedFunction("p.F").String(); strings.Contains(body, "@p.constant(") || !strings.Contains(body, "ret i64 42") {
t.Fatal("serialized bitcode bypassed pre-link optimization")
}
optimized, err := os.ReadFile(output + ".opt.ll")
if err != nil {
t.Fatal(err)
}
if bytes.Contains(optimized, []byte("call goabiinternal i64 @p.constant(")) {
t.Fatal("optimized IR still contains the package-local call")
}
index := func(ir, name string) string {
t.Helper()
definition := regexp.MustCompile(`(?m)^define [^\n]*@` + regexp.QuoteMeta(name) + `\([^\n]*!goobj.symbol.index !([0-9]+)`)
ref := definition.FindStringSubmatch(ir)
if ref == nil {
t.Fatalf("missing indexed definition %s", name)
}
node := regexp.MustCompile(`(?m)^!` + ref[1] + ` = !\{([^\n]+)\}$`).FindStringSubmatch(ir)
if node == nil {
t.Fatalf("missing symbol index metadata for %s", name)
}
return node[1]
}
for _, name := range []string{"p.F", "p.constant"} {
if got, want := index(module.String(), name), index(string(ir), name); got != want {
t.Fatalf("pre-link optimization changed %s index from %s to %s", name, want, got)
}
}

// Export data must remain usable without asking the importer to read
Expand Down
24 changes: 15 additions & 9 deletions src/cmd/compile/internal/ssa/llvmbackend.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import (
"github.com/goallc/go-llvm"
)

// EmitLLVMObject returns a GoObj linker object, or pre-optimization bitcode
// when -llvm-lto defers optimization and code generation to cmd/link.
// EmitLLVMObject returns a GoObj linker object, or pre-link optimized bitcode
// when -llvm-lto defers whole-program optimization and code generation to cmd/link.
func EmitLLVMObject(outputFile string) ([]byte, error) {
finalizeLLVMModule()
if err := llvm.ConfigureGoObjFromModule(CurrentModule); err != nil {
Expand Down Expand Up @@ -84,14 +84,11 @@ func EmitLLVMObject(outputFile string) ([]byte, error) {
}
}

if base.Flag.LLVMLTO {
buffer := llvm.WriteBitcodeToMemoryBuffer(CurrentModule)
defer buffer.Dispose()
return append([]byte(nil), buffer.Bytes()...), nil
}

pipeline := strings.TrimSpace(base.Flag.LLVMOptPasses)
if pipeline == "auto" {
if base.Flag.LLVMLTO {
// Use LLVM's pre-link policy, leaving whole-program decisions to LTO.
pipeline = "lto-pre-link<O2>"
} else if pipeline == "auto" {
pipeline = "default<O2>"
if base.Flag.N != 0 {
pipeline = "default<O0>"
Expand All @@ -110,6 +107,15 @@ func EmitLLVMObject(outputFile string) ([]byte, error) {
}
}

if base.Flag.LLVMLTO {
if err := llvm.VerifyModule(CurrentModule, llvm.ReturnStatusAction); err != nil {
return nil, fmt.Errorf("verify LLVM module after pre-link optimization: %w", err)
}
buffer := llvm.WriteBitcodeToMemoryBuffer(CurrentModule)
defer buffer.Dispose()
return append([]byte(nil), buffer.Bytes()...), nil
}

if err := tm.RunPassPluginPreCodeGen(CurrentModule, llvm.ObjectFile, plugin); err != nil {
return nil, fmt.Errorf("run GoALLC pre-codegen plugin: %w", err)
}
Expand Down
Loading