Files
llgo/internal/build/build.go

885 lines
24 KiB
Go
Raw Normal View History

2024-04-24 07:55:51 +08:00
/*
* Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package build
import (
"bytes"
"debug/macho"
2024-04-24 11:13:17 +08:00
"fmt"
"go/ast"
"go/build"
"go/constant"
2024-04-24 11:13:17 +08:00
"go/token"
2024-04-27 17:39:25 +08:00
"go/types"
2024-06-16 21:32:11 +08:00
"log"
2024-04-24 07:55:51 +08:00
"os"
2024-04-25 01:41:44 +08:00
"os/exec"
2024-04-25 00:53:42 +08:00
"path"
"path/filepath"
"runtime"
2024-04-24 07:55:51 +08:00
"strings"
"unsafe"
2024-04-24 07:55:51 +08:00
"golang.org/x/tools/go/ssa"
"github.com/goplus/llgo/cl"
"github.com/goplus/llgo/internal/env"
"github.com/goplus/llgo/internal/packages"
2024-06-28 15:14:30 +08:00
"github.com/goplus/llgo/internal/typepatch"
"github.com/goplus/llgo/ssa/abi"
xenv "github.com/goplus/llgo/xtool/env"
"github.com/goplus/llgo/xtool/env/llvm"
2024-04-25 00:53:42 +08:00
llssa "github.com/goplus/llgo/ssa"
2024-06-15 12:43:05 +08:00
clangCheck "github.com/goplus/llgo/xtool/clang/check"
2024-04-24 07:55:51 +08:00
)
type Mode int
const (
ModeBuild Mode = iota
ModeInstall
2024-04-25 01:41:44 +08:00
ModeRun
2024-06-23 00:48:20 +08:00
ModeCmpTest
ModeGen
2024-04-24 07:55:51 +08:00
)
const (
debugBuild = packages.DebugPackagesLoad
)
2024-04-25 01:41:44 +08:00
func needLLFile(mode Mode) bool {
return mode != ModeBuild
}
2024-04-25 00:53:42 +08:00
type Config struct {
BinPath string
AppExt string // ".exe" on Windows, empty on Unix
OutFile string // only valid for ModeBuild when len(pkgs) == 1
RunArgs []string // only valid for ModeRun
Mode Mode
2024-08-14 09:47:40 +08:00
GenExpect bool // only valid for ModeCmpTest
2024-04-25 00:53:42 +08:00
}
func NewDefaultConf(mode Mode) *Config {
bin := os.Getenv("GOBIN")
if bin == "" {
gopath, err := envGOPATH()
if err != nil {
panic(fmt.Errorf("cannot get GOPATH: %v", err))
}
bin = filepath.Join(gopath, "bin")
2024-04-25 00:53:42 +08:00
}
if err := os.MkdirAll(bin, 0755); err != nil {
panic(fmt.Errorf("cannot create bin directory: %v", err))
}
2024-04-25 00:53:42 +08:00
conf := &Config{
BinPath: bin,
Mode: mode,
2024-04-25 01:41:44 +08:00
AppExt: DefaultAppExt(),
2024-04-25 00:53:42 +08:00
}
2024-04-25 01:41:44 +08:00
return conf
}
func envGOPATH() (string, error) {
if gopath := os.Getenv("GOPATH"); gopath != "" {
return gopath, nil
}
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, "go"), nil
}
2024-04-25 01:41:44 +08:00
func DefaultAppExt() string {
2024-04-25 00:53:42 +08:00
if runtime.GOOS == "windows" {
2024-04-25 01:41:44 +08:00
return ".exe"
2024-04-25 00:53:42 +08:00
}
2024-04-25 01:41:44 +08:00
return ""
2024-04-25 00:53:42 +08:00
}
2024-04-24 07:55:51 +08:00
// -----------------------------------------------------------------------------
const (
loadFiles = packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles
loadImports = loadFiles | packages.NeedImports
loadTypes = loadImports | packages.NeedTypes | packages.NeedTypesSizes
loadSyntax = loadTypes | packages.NeedSyntax | packages.NeedTypesInfo
)
func Do(args []string, conf *Config) ([]Package, error) {
flags, patterns, verbose := ParseArgs(args, buildFlags)
2024-09-04 10:07:22 +08:00
flags = append(flags, "-tags", "llgo")
2024-04-24 07:55:51 +08:00
cfg := &packages.Config{
Mode: loadSyntax | packages.NeedDeps | packages.NeedModule | packages.NeedExportFile,
2024-04-24 07:55:51 +08:00
BuildFlags: flags,
Fset: token.NewFileSet(),
2024-04-24 07:55:51 +08:00
}
if len(overlayFiles) > 0 {
cfg.Overlay = make(map[string][]byte)
for file, src := range overlayFiles {
overlay := unsafe.Slice(unsafe.StringData(src), len(src))
cfg.Overlay[filepath.Join(runtime.GOROOT(), "src", file)] = overlay
}
}
2024-11-26 14:39:29 +08:00
cl.EnableDebugSymbols(IsDebugEnabled())
llssa.Initialize(llssa.InitAll)
target := &llssa.Target{
GOOS: build.Default.GOOS,
GOARCH: build.Default.GOARCH,
}
prog := llssa.NewProgram(target)
sizes := prog.TypeSizes
2024-06-15 21:12:43 +08:00
dedup := packages.NewDeduper()
dedup.SetPreload(func(pkg *types.Package, files []*ast.File) {
if canSkipToBuild(pkg.Path()) {
return
}
cl.ParsePkgSyntax(prog, pkg, files)
})
2024-04-24 07:55:51 +08:00
if patterns == nil {
patterns = []string{"."}
}
2024-06-15 20:46:29 +08:00
initial, err := packages.LoadEx(dedup, sizes, cfg, patterns...)
2024-04-24 07:55:51 +08:00
check(err)
2024-04-29 01:34:21 +08:00
mode := conf.Mode
if len(initial) == 1 && len(initial[0].CompiledGoFiles) > 0 {
if mode == ModeBuild {
mode = ModeInstall
}
} else if mode == ModeRun {
if len(initial) > 1 {
return nil, fmt.Errorf("cannot run multiple packages")
2024-04-29 01:34:21 +08:00
} else {
return nil, fmt.Errorf("no Go files in matched packages")
2024-04-29 01:34:21 +08:00
}
}
altPkgPaths := altPkgs(initial, llssa.PkgRuntime)
// Use LLGoROOT as default implementation if `github.com/goplus/llgo` is not
// imported in user's go.mod. This ensures compilation works without import
// while allowing `github.com/goplus/llgo` upgrades via go.mod.
//
// WARNING(lijie): This approach cannot guarantee compatibility between `llgo`
// executable and runtime. This is a known design limitation that needs to be
// addressed in future improvements. The runtime should be:
// 1. Released and fully tested with the `llgo` compiler across different Go
// compiler versions and user-specified go versions in go.mod
// 2. Not be dependent on `github.com/goplus/llgo/c` library. Current runtime directly
// depends on it, causing version conflicts: using LLGoROOT makes user's specified
// version ineffective, while not using it leaves runtime unable to follow compiler
// updates. Since `github.com/goplus/llgo/c/*` contains many application libraries
// that may change frequently, a possible solution is to have both depend on a
// stable and limited c core API.
if !llgoPkgImported(initial) {
cfg.Dir = env.LLGoROOT()
}
altPkgs, err := packages.LoadEx(dedup, sizes, cfg, altPkgPaths...)
check(err)
noRt := 1
2024-04-27 17:39:25 +08:00
prog.SetRuntime(func() *types.Package {
noRt = 0
return altPkgs[0].Types
2024-04-27 17:39:25 +08:00
})
prog.SetPython(func() *types.Package {
return dedup.Check(llssa.PkgPython).Types
})
2024-04-27 22:13:40 +08:00
buildMode := ssaBuildMode
2024-11-26 14:39:29 +08:00
if IsDebugEnabled() {
buildMode |= ssa.GlobalDebug
}
if !IsOptimizeEnabled() {
buildMode |= ssa.NaiveForm
}
progSSA := ssa.NewProgram(initial[0].Fset, buildMode)
patches := make(cl.Patches, len(altPkgPaths))
altSSAPkgs(progSSA, patches, altPkgs[1:], verbose)
env := llvm.New("")
os.Setenv("PATH", env.BinDir()+":"+os.Getenv("PATH")) // TODO(xsw): check windows
2024-11-15 12:24:46 +08:00
ctx := &context{env, cfg, progSSA, prog, dedup, patches, make(map[string]none), initial, mode, 0}
2024-06-21 15:31:18 +08:00
pkgs := buildAllPkgs(ctx, initial, verbose)
if mode == ModeGen {
for _, pkg := range pkgs {
if pkg.Package == initial[0] {
return []*aPackage{pkg}, nil
}
}
return nil, fmt.Errorf("initial package not found")
}
2024-04-27 22:13:40 +08:00
2024-06-21 15:31:18 +08:00
dpkg := buildAllPkgs(ctx, altPkgs[noRt:], verbose)
var linkArgs []string
for _, pkg := range dpkg {
linkArgs = append(linkArgs, pkg.LinkArgs...)
2024-04-24 11:49:43 +08:00
}
if mode != ModeBuild {
2024-04-29 01:34:21 +08:00
nErr := 0
2024-04-25 00:53:42 +08:00
for _, pkg := range initial {
if pkg.Name == "main" {
nErr += linkMainPkg(ctx, pkg, pkgs, linkArgs, conf, mode, verbose)
2024-04-25 00:53:42 +08:00
}
}
2024-04-29 01:34:21 +08:00
if nErr > 0 {
2024-04-30 16:15:36 +08:00
os.Exit(nErr)
2024-04-29 01:34:21 +08:00
}
2024-04-24 11:13:17 +08:00
}
return dpkg, nil
2024-04-24 11:13:17 +08:00
}
func llgoPkgImported(pkgs []*packages.Package) bool {
for _, pkg := range pkgs {
for _, imp := range pkg.Imports {
if imp.Module != nil && imp.Module.Path == env.LLGoCompilerPkg {
return true
}
}
}
return false
}
2024-05-12 11:11:19 +08:00
func setNeedRuntimeOrPyInit(pkg *packages.Package, needRuntime, needPyInit bool) {
v := []byte{'0', '0'}
if needRuntime {
v[0] = '1'
}
if needPyInit {
v[1] = '1'
}
pkg.ID = string(v) // just use pkg.ID to mark it needs runtime
2024-04-29 00:49:17 +08:00
}
2024-05-12 11:11:19 +08:00
func isNeedRuntimeOrPyInit(pkg *packages.Package) (needRuntime, needPyInit bool) {
if len(pkg.ID) == 2 {
return pkg.ID[0] == '1', pkg.ID[1] == '1'
}
return
2024-04-29 00:49:17 +08:00
}
2024-06-15 17:40:05 +08:00
const (
ssaBuildMode = ssa.SanityCheckFunctions | ssa.InstantiateGenerics
2024-06-15 17:40:05 +08:00
)
type context struct {
env *llvm.Env
2024-11-15 12:24:46 +08:00
conf *packages.Config
progSSA *ssa.Program
prog llssa.Program
dedup packages.Deduper
patches cl.Patches
built map[string]none
2024-06-21 15:45:29 +08:00
initial []*packages.Package
mode Mode
2024-07-15 10:13:01 +08:00
nLibdir int
}
2024-06-21 15:31:18 +08:00
func buildAllPkgs(ctx *context, initial []*packages.Package, verbose bool) (pkgs []*aPackage) {
prog := ctx.prog
2024-06-21 15:31:18 +08:00
pkgs, errPkgs := allPkgs(ctx, initial, verbose)
2024-04-27 22:13:40 +08:00
for _, errPkg := range errPkgs {
2024-04-30 15:58:01 +08:00
for _, err := range errPkg.Errors {
fmt.Fprintln(os.Stderr, err)
}
2024-04-29 01:34:21 +08:00
fmt.Fprintln(os.Stderr, "cannot build SSA for package", errPkg)
2024-04-27 22:13:40 +08:00
}
2024-09-04 09:55:56 +08:00
if len(errPkgs) > 0 {
os.Exit(1)
}
built := ctx.built
for _, aPkg := range pkgs {
pkg := aPkg.Package
if _, ok := built[pkg.PkgPath]; ok {
2024-06-07 21:02:01 +08:00
pkg.ExportFile = ""
continue
}
built[pkg.PkgPath] = none{}
2024-05-09 14:51:01 +08:00
switch kind, param := cl.PkgKindOf(pkg.Types); kind {
case cl.PkgDeclOnly:
// skip packages that only contain declarations
// and set no export file
pkg.ExportFile = ""
2024-05-12 13:05:15 +08:00
case cl.PkgLinkIR, cl.PkgLinkExtern, cl.PkgPyModule:
2024-07-11 11:37:55 +08:00
if len(pkg.GoFiles) > 0 {
cgoLdflags, err := buildPkg(ctx, aPkg, verbose)
if err != nil {
panic(err)
}
linkParts := concatPkgLinkFiles(ctx, pkg, verbose)
allParts := append(linkParts, cgoLdflags...)
allParts = append(allParts, pkg.ExportFile)
aPkg.LinkArgs = allParts
} else {
2024-05-14 08:40:42 +08:00
// panic("todo")
// TODO(xsw): support packages out of llgo
pkg.ExportFile = ""
}
if kind == cl.PkgLinkExtern {
// need to be linked with external library
// format: ';' separated alternative link methods. e.g.
// link: $LLGO_LIB_PYTHON; $(pkg-config --libs python3-embed); -lpython3
altParts := strings.Split(param, ";")
expdArgs := make([]string, 0, len(altParts))
for _, param := range altParts {
2024-07-15 10:13:01 +08:00
param = strings.TrimSpace(param)
if strings.ContainsRune(param, '$') {
expdArgs = append(expdArgs, xenv.ExpandEnvToArgs(param)...)
2024-07-15 10:13:01 +08:00
ctx.nLibdir++
} else {
2024-12-08 10:18:47 +08:00
fields := strings.Fields(param)
expdArgs = append(expdArgs, fields...)
2024-07-15 10:13:01 +08:00
}
if len(expdArgs) > 0 {
break
}
}
if len(expdArgs) == 0 {
panic(fmt.Sprintf("'%s' cannot locate the external library", param))
}
pkgLinkArgs := make([]string, 0, 3)
if expdArgs[0][0] == '-' {
pkgLinkArgs = append(pkgLinkArgs, expdArgs...)
} else {
linkFile := expdArgs[0]
dir, lib := filepath.Split(linkFile)
pkgLinkArgs = append(pkgLinkArgs, "-l"+lib)
if dir != "" {
pkgLinkArgs = append(pkgLinkArgs, "-L"+dir)
2024-07-15 10:13:01 +08:00
ctx.nLibdir++
}
}
if err := clangCheck.CheckLinkArgs(pkgLinkArgs); err != nil {
panic(fmt.Sprintf("test link args '%s' failed\n\texpanded to: %v\n\tresolved to: %v\n\terror: %v", param, expdArgs, pkgLinkArgs, err))
2024-05-12 13:05:15 +08:00
}
aPkg.LinkArgs = append(aPkg.LinkArgs, pkgLinkArgs...)
2024-05-09 14:51:01 +08:00
}
default:
cgoLdflags, err := buildPkg(ctx, aPkg, verbose)
if err != nil {
panic(err)
}
aPkg.LinkArgs = append(cgoLdflags, pkg.ExportFile)
2024-05-12 15:42:50 +08:00
setNeedRuntimeOrPyInit(pkg, prog.NeedRuntime, prog.NeedPyInit)
2024-04-29 00:49:17 +08:00
}
2024-04-27 22:13:40 +08:00
}
return
2024-04-27 22:13:40 +08:00
}
func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, linkArgs []string, conf *Config, mode Mode, verbose bool) (nErr int) {
2024-04-25 01:41:44 +08:00
pkgPath := pkg.PkgPath
name := path.Base(pkgPath)
app := conf.OutFile
if app == "" {
app = filepath.Join(conf.BinPath, name+conf.AppExt)
}
args := make([]string, 0, len(pkg.Imports)+len(linkArgs)+16)
args = append(
args,
"-o", app,
"-fuse-ld=lld",
"-Wno-override-module",
// "-O2", // FIXME: This will cause TestFinalizer in _test/bdwgc.go to fail on macOS.
)
switch runtime.GOOS {
case "darwin": // ld64.lld (macOS)
args = append(
args,
2024-07-15 22:45:15 +08:00
"-rpath", "@loader_path",
"-rpath", "@loader_path/../lib",
"-Xlinker", "-dead_strip",
)
case "windows": // lld-link (Windows)
// TODO: Add options for Windows.
default: // ld.lld (Unix), wasm-ld (WebAssembly)
args = append(
args,
2024-07-15 22:45:15 +08:00
"-rpath", "$ORIGIN",
"-rpath", "$ORIGIN/../lib",
"-fdata-sections",
"-ffunction-sections",
"-Xlinker", "--gc-sections",
"-lm",
"-latomic",
"-lpthread", // libpthread is built-in since glibc 2.34 (2021-08-01); we need to support earlier versions.
)
}
2024-04-29 00:49:17 +08:00
needRuntime := false
2024-05-12 11:11:19 +08:00
needPyInit := false
pkgsMap := make(map[*packages.Package]*aPackage, len(pkgs))
for _, v := range pkgs {
pkgsMap[v.Package] = v
}
2024-04-25 00:53:42 +08:00
packages.Visit([]*packages.Package{pkg}, nil, func(p *packages.Package) {
2024-06-07 15:21:27 +08:00
if p.ExportFile != "" { // skip packages that only contain declarations
aPkg := pkgsMap[p]
args = append(args, aPkg.LinkArgs...)
2024-05-12 11:11:19 +08:00
need1, need2 := isNeedRuntimeOrPyInit(p)
2024-04-29 00:49:17 +08:00
if !needRuntime {
2024-05-12 11:11:19 +08:00
needRuntime = need1
}
if !needPyInit {
needPyInit = need2
2024-04-29 00:49:17 +08:00
}
2024-04-25 00:53:42 +08:00
}
})
2024-05-12 11:11:19 +08:00
var aPkg *aPackage
for _, v := range pkgs {
if v.Package == pkg { // found this package
aPkg = v
break
}
}
dirty := false
2024-07-02 18:39:07 +08:00
if needRuntime {
args = append(args, linkArgs...)
} else {
2024-05-12 11:11:19 +08:00
dirty = true
fn := aPkg.LPkg.FuncOf(cl.RuntimeInit)
fn.MakeBody(1).Return()
}
if needPyInit {
dirty = aPkg.LPkg.PyInit()
}
if dirty && needLLFile(mode) {
lpkg := aPkg.LPkg
os.WriteFile(pkg.ExportFile, []byte(lpkg.String()), 0644)
2024-04-29 00:49:17 +08:00
}
2024-04-25 00:53:42 +08:00
2024-04-29 01:34:21 +08:00
if verbose || mode != ModeRun {
fmt.Fprintln(os.Stderr, "#", pkgPath)
}
2024-04-29 01:34:21 +08:00
defer func() {
if e := recover(); e != nil {
nErr = 1
}
}()
// add rpath and find libs
2024-07-15 10:13:01 +08:00
exargs := make([]string, 0, ctx.nLibdir<<1)
libs := make([]string, 0, ctx.nLibdir*3)
2024-07-15 10:13:01 +08:00
for _, arg := range args {
if strings.HasPrefix(arg, "-L") {
exargs = append(exargs, "-rpath", arg[2:])
} else if strings.HasPrefix(arg, "-l") {
libs = append(libs, arg[2:])
2024-07-15 10:13:01 +08:00
}
}
args = append(args, exargs...)
2024-11-26 14:39:29 +08:00
if IsDebugEnabled() {
2024-09-30 15:22:38 +08:00
args = append(args, "-gdwarf-4")
2024-09-13 17:15:36 +08:00
}
2024-07-15 10:13:01 +08:00
2024-04-29 01:34:21 +08:00
// TODO(xsw): show work
2024-05-01 13:30:13 +08:00
if verbose {
fmt.Fprintln(os.Stderr, "clang", args)
}
err := ctx.env.Clang().Exec(args...)
2024-04-25 00:53:42 +08:00
check(err)
2024-04-25 01:41:44 +08:00
if runtime.GOOS == "darwin" {
dylibDeps := make([]string, 0, len(libs))
for _, lib := range libs {
dylibDep := findDylibDep(app, lib)
if dylibDep != "" {
dylibDeps = append(dylibDeps, dylibDep)
}
}
err := ctx.env.InstallNameTool().ChangeToRpath(app, dylibDeps...)
check(err)
}
2024-06-23 00:48:20 +08:00
switch mode {
case ModeRun:
2024-04-25 01:41:44 +08:00
cmd := exec.Command(app, conf.RunArgs...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Run()
2024-06-12 20:31:42 +08:00
if s := cmd.ProcessState; s != nil {
os.Exit(s.ExitCode())
}
2024-06-23 00:48:20 +08:00
case ModeCmpTest:
cmpTest(filepath.Dir(pkg.GoFiles[0]), pkgPath, app, conf.GenExpect, conf.RunArgs)
2024-04-25 01:41:44 +08:00
}
2024-04-29 01:34:21 +08:00
return
2024-04-25 00:53:42 +08:00
}
func buildPkg(ctx *context, aPkg *aPackage, verbose bool) (cgoLdflags []string, err error) {
2024-04-25 00:53:42 +08:00
pkg := aPkg.Package
2024-04-24 11:13:17 +08:00
pkgPath := pkg.PkgPath
2024-06-21 15:31:18 +08:00
if debugBuild || verbose {
2024-04-25 01:41:44 +08:00
fmt.Fprintln(os.Stderr, pkgPath)
}
2024-05-09 14:19:31 +08:00
if canSkipToBuild(pkgPath) {
pkg.ExportFile = ""
2024-04-25 00:53:42 +08:00
return
2024-04-24 11:13:17 +08:00
}
var syntax = pkg.Syntax
2024-06-15 12:10:08 +08:00
if altPkg := aPkg.AltPkg; altPkg != nil {
syntax = append(syntax, altPkg.Syntax...)
}
2024-06-21 15:45:29 +08:00
showDetail := verbose && pkgExists(ctx.initial, pkg)
if showDetail {
llssa.SetDebug(llssa.DbgFlagAll)
cl.SetDebug(cl.DbgFlagAll)
}
2024-07-07 11:45:46 +08:00
ret, externs, err := cl.NewPackageEx(ctx.prog, ctx.patches, aPkg.SSA, syntax)
2024-06-21 15:45:29 +08:00
if showDetail {
llssa.SetDebug(0)
cl.SetDebug(0)
}
2024-04-24 11:13:17 +08:00
check(err)
2024-11-26 22:34:19 +08:00
aPkg.LPkg = ret
cgoLdflags, err = buildCgo(ctx, aPkg, aPkg.Package.Syntax, externs, verbose)
if needLLFile(ctx.mode) {
pkg.ExportFile += ".ll"
os.WriteFile(pkg.ExportFile, []byte(ret.String()), 0644)
2024-06-21 15:31:18 +08:00
if debugBuild || verbose {
fmt.Fprintf(os.Stderr, "==> Export %s: %s\n", aPkg.PkgPath, pkg.ExportFile)
}
if IsCheckEnable() {
if err, msg := llcCheck(ctx.env, pkg.ExportFile); err != nil {
fmt.Fprintf(os.Stderr, "==> lcc %v: %v\n%v\n", pkg.PkgPath, pkg.ExportFile, msg)
}
}
}
return
}
func llcCheck(env *llvm.Env, exportFile string) (err error, msg string) {
bin := filepath.Join(env.BinDir(), "llc")
cmd := exec.Command(bin, "-filetype=null", exportFile)
var buf bytes.Buffer
cmd.Stderr = &buf
if err = cmd.Run(); err != nil {
msg = buf.String()
2024-04-24 11:13:17 +08:00
}
return
2024-04-24 11:13:17 +08:00
}
const (
altPkgPathPrefix = abi.PatchPathPrefix
)
2024-06-15 11:44:52 +08:00
func altPkgs(initial []*packages.Package, alts ...string) []string {
2024-04-24 11:13:17 +08:00
packages.Visit(initial, nil, func(p *packages.Package) {
if p.Types != nil && !p.IllTyped {
if _, ok := hasAltPkg[p.PkgPath]; ok {
alts = append(alts, altPkgPathPrefix+p.PkgPath)
}
}
})
return alts
}
func altSSAPkgs(prog *ssa.Program, patches cl.Patches, alts []*packages.Package, verbose bool) {
packages.Visit(alts, nil, func(p *packages.Package) {
2024-06-28 15:14:30 +08:00
if typs := p.Types; typs != nil && !p.IllTyped {
if debugBuild || verbose {
log.Println("==> BuildSSA", p.PkgPath)
}
2024-06-28 15:14:30 +08:00
pkgSSA := prog.CreatePackage(typs, p.Syntax, p.TypesInfo, true)
if strings.HasPrefix(p.PkgPath, altPkgPathPrefix) {
path := p.PkgPath[len(altPkgPathPrefix):]
2024-06-28 15:14:30 +08:00
patches[path] = cl.Patch{Alt: pkgSSA, Types: typepatch.Clone(typs)}
if debugBuild || verbose {
log.Println("==> Patching", path)
2024-06-15 11:44:52 +08:00
}
}
2024-04-24 11:13:17 +08:00
}
})
prog.Build()
2024-04-24 07:55:51 +08:00
}
type aPackage struct {
*packages.Package
SSA *ssa.Package
AltPkg *packages.Cached
LPkg llssa.Package
LinkArgs []string
}
type Package = *aPackage
2024-06-21 15:31:18 +08:00
func allPkgs(ctx *context, initial []*packages.Package, verbose bool) (all []*aPackage, errs []*packages.Package) {
prog := ctx.progSSA
built := ctx.built
packages.Visit(initial, nil, func(p *packages.Package) {
if p.Types != nil && !p.IllTyped {
2024-06-17 05:34:52 +08:00
pkgPath := p.PkgPath
if _, ok := built[pkgPath]; ok || strings.HasPrefix(pkgPath, altPkgPathPrefix) {
return
2024-06-15 13:08:11 +08:00
}
var altPkg *packages.Cached
var ssaPkg = createSSAPkg(prog, p, verbose)
2024-06-17 05:34:52 +08:00
if _, ok := hasAltPkg[pkgPath]; ok {
if altPkg = ctx.dedup.Check(altPkgPathPrefix + pkgPath); altPkg == nil {
2024-06-17 05:33:07 +08:00
return
}
}
all = append(all, &aPackage{p, ssaPkg, altPkg, nil, nil})
} else {
errs = append(errs, p)
}
})
return
2024-06-15 13:08:11 +08:00
}
func createSSAPkg(prog *ssa.Program, p *packages.Package, verbose bool) *ssa.Package {
2024-06-16 22:47:57 +08:00
pkgSSA := prog.ImportedPackage(p.PkgPath)
if pkgSSA == nil {
if debugBuild || verbose {
log.Println("==> BuildSSA", p.PkgPath)
}
2024-06-16 22:47:57 +08:00
pkgSSA = prog.CreatePackage(p.Types, p.Syntax, p.TypesInfo, true)
pkgSSA.Build() // TODO(xsw): build concurrently
}
return pkgSSA
}
var (
// TODO(xsw): complete build flags
buildFlags = map[string]bool{
"-C": true, // -C dir: Change to dir before running the command
"-a": false, // -a: force rebuilding of packages that are already up-to-date
"-n": false, // -n: print the commands but do not run them
"-p": true, // -p n: the number of programs to run in parallel
"-race": false, // -race: enable data race detection
"-cover": false, // -cover: enable coverage analysis
"-covermode": true, // -covermode mode: set the mode for coverage analysis
"-v": false, // -v: print the names of packages as they are compiled
"-work": false, // -work: print the name of the temporary work directory and do not delete it when exiting
"-x": false, // -x: print the commands
"-tags": true, // -tags 'tag,list': a space-separated list of build tags to consider satisfied during the build
"-pkgdir": true, // -pkgdir dir: install and load all packages from dir instead of the usual locations
2024-05-17 13:54:00 +08:00
"-ldflags": true, // --ldflags 'flag list': arguments to pass on each go tool link invocation
}
)
const llgoDebug = "LLGO_DEBUG"
const llgoOptimize = "LLGO_OPTIMIZE"
const llgoCheck = "LLGO_CHECK"
func isEnvOn(env string, defVal bool) bool {
envVal := strings.ToLower(os.Getenv(env))
if envVal == "" {
return defVal
}
return envVal == "1" || envVal == "true" || envVal == "on"
}
func IsDebugEnabled() bool {
return isEnvOn(llgoDebug, false)
}
func IsOptimizeEnabled() bool {
return isEnvOn(llgoOptimize, true)
}
func IsCheckEnable() bool {
return isEnvOn(llgoCheck, false)
}
func ParseArgs(args []string, swflags map[string]bool) (flags, patterns []string, verbose bool) {
n := len(args)
for i := 0; i < n; i++ {
arg := args[i]
if strings.HasPrefix(arg, "-") {
checkFlag(arg, &i, &verbose, swflags)
} else {
2025-01-05 15:41:55 +08:00
patterns = append([]string{}, args[i:]...)
flags = append([]string{}, args[:i]...)
2024-04-24 14:27:14 +08:00
return
}
2024-04-24 07:55:51 +08:00
}
2024-04-24 14:27:14 +08:00
return
2024-04-24 07:55:51 +08:00
}
2024-04-27 06:41:24 +08:00
func SkipFlagArgs(args []string) int {
n := len(args)
for i := 0; i < n; i++ {
arg := args[i]
if strings.HasPrefix(arg, "-") {
checkFlag(arg, &i, nil, buildFlags)
} else {
return i
}
}
return -1
}
func checkFlag(arg string, i *int, verbose *bool, swflags map[string]bool) {
2024-05-17 13:54:00 +08:00
if pos := strings.IndexByte(arg, '='); pos > 0 {
if verbose != nil && arg == "-v=true" {
*verbose = true
}
} else if hasarg, ok := swflags[arg]; ok {
if hasarg {
*i++
} else if verbose != nil && arg == "-v" {
*verbose = true
}
} else {
panic("unknown flag: " + arg)
}
}
func concatPkgLinkFiles(ctx *context, pkg *packages.Package, verbose bool) (parts []string) {
llgoPkgLinkFiles(ctx, pkg, func(linkFile string) {
parts = append(parts, linkFile)
}, verbose)
return
}
// const LLGoFiles = "file1; file2; ..."
func llgoPkgLinkFiles(ctx *context, pkg *packages.Package, procFile func(linkFile string), verbose bool) {
if o := pkg.Types.Scope().Lookup("LLGoFiles"); o != nil {
val := o.(*types.Const).Val()
if val.Kind() == constant.String {
clFiles(ctx, constant.StringVal(val), pkg, procFile, verbose)
}
}
}
// files = "file1; file2; ..."
// files = "$(pkg-config --cflags xxx): file1; file2; ..."
func clFiles(ctx *context, files string, pkg *packages.Package, procFile func(linkFile string), verbose bool) {
2024-06-17 12:14:24 +08:00
dir := filepath.Dir(pkg.GoFiles[0])
expFile := pkg.ExportFile
args := make([]string, 0, 16)
if strings.HasPrefix(files, "$") { // has cflags
if pos := strings.IndexByte(files, ':'); pos > 0 {
cflags := xenv.ExpandEnvToArgs(files[:pos])
files = files[pos+1:]
args = append(args, cflags...)
}
}
for _, file := range strings.Split(files, ";") {
cFile := filepath.Join(dir, strings.TrimSpace(file))
clFile(ctx, args, cFile, expFile, procFile, verbose)
}
}
func clFile(ctx *context, args []string, cFile, expFile string, procFile func(linkFile string), verbose bool) {
llFile := expFile + filepath.Base(cFile) + ".ll"
args = append(args, "-emit-llvm", "-S", "-o", llFile, "-c", cFile)
if verbose {
fmt.Fprintln(os.Stderr, "clang", args)
}
err := ctx.env.Clang().Exec(args...)
check(err)
procFile(llFile)
}
2024-06-21 15:45:29 +08:00
func pkgExists(initial []*packages.Package, pkg *packages.Package) bool {
for _, v := range initial {
if v == pkg {
return true
}
}
return false
}
2024-06-17 05:33:07 +08:00
func canSkipToBuild(pkgPath string) bool {
2024-06-20 11:05:43 +08:00
if _, ok := hasAltPkg[pkgPath]; ok {
return false
}
2024-06-17 05:33:07 +08:00
switch pkgPath {
2024-06-18 13:50:55 +08:00
case "unsafe":
2024-06-17 05:33:07 +08:00
return true
default:
return strings.HasPrefix(pkgPath, "internal/") ||
strings.HasPrefix(pkgPath, "runtime/internal/")
}
}
// findDylibDep finds the dylib dependency in the executable. It returns empty
// string if not found.
func findDylibDep(exe, lib string) string {
file, err := macho.Open(exe)
check(err)
defer file.Close()
for _, load := range file.Loads {
if dylib, ok := load.(*macho.Dylib); ok {
if strings.HasPrefix(filepath.Base(dylib.Name), fmt.Sprintf("lib%s.", lib)) {
return dylib.Name
}
}
}
return ""
}
2024-06-17 05:33:07 +08:00
type none struct{}
var hasAltPkg = map[string]none{
"crypto/hmac": {},
2024-07-30 02:07:19 +08:00
"crypto/md5": {},
"crypto/rand": {},
2024-07-31 13:56:42 +08:00
"crypto/sha1": {},
"crypto/sha256": {},
"crypto/sha512": {},
"crypto/subtle": {},
2024-07-12 00:39:16 +08:00
"fmt": {},
2024-07-30 19:36:36 +08:00
"hash/crc32": {},
2024-07-12 00:39:16 +08:00
"internal/abi": {},
"internal/bytealg": {},
2024-07-28 23:53:22 +08:00
"internal/itoa": {},
2024-12-31 21:02:23 +08:00
"internal/filepathlite": {},
2024-07-12 00:39:16 +08:00
"internal/oserror": {},
2024-11-29 14:23:46 +08:00
"internal/race": {},
2024-07-12 00:39:16 +08:00
"internal/reflectlite": {},
2024-11-29 14:23:46 +08:00
"internal/stringslite": {},
2024-07-12 00:39:16 +08:00
"internal/syscall/execenv": {},
2024-07-19 23:19:59 +08:00
"internal/syscall/unix": {},
2024-07-12 00:39:16 +08:00
"math": {},
"math/big": {},
2024-07-12 00:39:16 +08:00
"math/cmplx": {},
2024-07-30 21:41:26 +08:00
"math/rand": {},
2024-07-12 00:39:16 +08:00
"reflect": {},
"sync": {},
"sync/atomic": {},
"syscall": {},
"time": {},
"os": {},
"os/exec": {},
"runtime": {},
2024-07-28 16:52:42 +08:00
"io": {},
2024-06-17 05:33:07 +08:00
}
2024-04-24 07:55:51 +08:00
func check(err error) {
if err != nil {
panic(err)
}
}
// -----------------------------------------------------------------------------