Merge pull request #1396 from goplus/pr-1395
build: fix ldflags rewrites and prep caching
This commit is contained in:
11
cl/_testgo/rewrite/dep/dep.go
Normal file
11
cl/_testgo/rewrite/dep/dep.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package dep
|
||||
|
||||
import "fmt"
|
||||
|
||||
var VarName = "dep-default"
|
||||
var VarPlain string
|
||||
|
||||
func PrintVar() {
|
||||
fmt.Printf("dep.VarName: %s\n", VarName)
|
||||
fmt.Printf("dep.VarPlain: %s\n", VarPlain)
|
||||
}
|
||||
23
cl/_testgo/rewrite/main.go
Normal file
23
cl/_testgo/rewrite/main.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
dep "github.com/goplus/llgo/cl/_testgo/rewrite/dep"
|
||||
)
|
||||
|
||||
var VarName = "main-default"
|
||||
var VarPlain string
|
||||
|
||||
func printLine(label, value string) {
|
||||
fmt.Printf("%s: %s\n", label, value)
|
||||
}
|
||||
|
||||
func main() {
|
||||
printLine("main.VarName", VarName)
|
||||
printLine("main.VarPlain", VarPlain)
|
||||
dep.PrintVar()
|
||||
printLine("runtime.GOROOT()", runtime.GOROOT())
|
||||
printLine("runtime.Version()", runtime.Version())
|
||||
}
|
||||
1
cl/_testgo/rewrite/out.ll
Normal file
1
cl/_testgo/rewrite/out.ll
Normal file
@@ -0,0 +1 @@
|
||||
;
|
||||
@@ -127,6 +127,58 @@ type context struct {
|
||||
cgoArgs []llssa.Expr
|
||||
cgoRet llssa.Expr
|
||||
cgoSymbols []string
|
||||
rewrites map[string]string
|
||||
}
|
||||
|
||||
func (p *context) rewriteValue(name string) (string, bool) {
|
||||
if p.rewrites == nil {
|
||||
return "", false
|
||||
}
|
||||
dot := strings.LastIndex(name, ".")
|
||||
if dot < 0 || dot == len(name)-1 {
|
||||
return "", false
|
||||
}
|
||||
varName := name[dot+1:]
|
||||
val, ok := p.rewrites[varName]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// isStringPtrType checks if typ is a pointer to the basic string type (*string).
|
||||
// This is used to validate that -ldflags -X can only rewrite variables of type *string,
|
||||
// not derived string types like "type T string".
|
||||
func (p *context) isStringPtrType(typ types.Type) bool {
|
||||
ptr, ok := typ.(*types.Pointer)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
basic, ok := ptr.Elem().(*types.Basic)
|
||||
return ok && basic.Kind() == types.String
|
||||
}
|
||||
|
||||
func (p *context) globalFullName(g *ssa.Global) string {
|
||||
name, _, _ := p.varName(g.Pkg.Pkg, g)
|
||||
return name
|
||||
}
|
||||
|
||||
func (p *context) rewriteInitStore(store *ssa.Store, g *ssa.Global) (string, bool) {
|
||||
if p.rewrites == nil {
|
||||
return "", false
|
||||
}
|
||||
fn := store.Block().Parent()
|
||||
if fn == nil || fn.Synthetic != "package initializer" {
|
||||
return "", false
|
||||
}
|
||||
if _, ok := store.Val.(*ssa.Const); !ok {
|
||||
return "", false
|
||||
}
|
||||
if !p.isStringPtrType(g.Type()) {
|
||||
return "", false
|
||||
}
|
||||
value, ok := p.rewriteValue(p.globalFullName(g))
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return value, true
|
||||
}
|
||||
|
||||
type pkgState byte
|
||||
@@ -176,7 +228,16 @@ func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) {
|
||||
log.Println("==> NewVar", name, typ)
|
||||
}
|
||||
g := pkg.NewVar(name, typ, llssa.Background(vtype))
|
||||
if define {
|
||||
if value, ok := p.rewriteValue(name); ok {
|
||||
if p.isStringPtrType(gbl.Type()) {
|
||||
g.Init(pkg.ConstString(value))
|
||||
} else {
|
||||
log.Printf("warning: ignoring rewrite for non-string variable %s (type: %v)", name, gbl.Type())
|
||||
if define {
|
||||
g.InitNil()
|
||||
}
|
||||
}
|
||||
} else if define {
|
||||
g.InitNil()
|
||||
}
|
||||
}
|
||||
@@ -816,6 +877,13 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if p.rewrites != nil {
|
||||
if g, ok := va.(*ssa.Global); ok {
|
||||
if _, ok := p.rewriteInitStore(v, g); ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
ptr := p.compileValue(b, va)
|
||||
val := p.compileValue(b, v.Val)
|
||||
b.Store(ptr, val)
|
||||
@@ -980,12 +1048,22 @@ type Patches = map[string]Patch
|
||||
|
||||
// NewPackage compiles a Go package to LLVM IR package.
|
||||
func NewPackage(prog llssa.Program, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, err error) {
|
||||
ret, _, err = NewPackageEx(prog, nil, pkg, files)
|
||||
ret, _, err = NewPackageEx(prog, nil, nil, pkg, files)
|
||||
return
|
||||
}
|
||||
|
||||
// NewPackageEx compiles a Go package to LLVM IR package.
|
||||
func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, externs []string, err error) {
|
||||
//
|
||||
// Parameters:
|
||||
// - prog: target LLVM SSA program context
|
||||
// - patches: optional package patches applied during compilation
|
||||
// - rewrites: per-package string initializers rewritten at compile time
|
||||
// - pkg: SSA package to compile
|
||||
// - files: parsed AST files that belong to the package
|
||||
//
|
||||
// The rewrites map uses short variable names (without package qualifier) and
|
||||
// only affects string-typed globals defined in the current package.
|
||||
func NewPackageEx(prog llssa.Program, patches Patches, rewrites map[string]string, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, externs []string, err error) {
|
||||
pkgProg := pkg.Prog
|
||||
pkgTypes := pkg.Pkg
|
||||
oldTypes := pkgTypes
|
||||
@@ -1018,6 +1096,7 @@ func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files [
|
||||
types.Unsafe: {kind: PkgDeclOnly}, // TODO(xsw): PkgNoInit or PkgDeclOnly?
|
||||
},
|
||||
cgoSymbols: make([]string, 0, 128),
|
||||
rewrites: rewrites,
|
||||
}
|
||||
ctx.initPyModule()
|
||||
ctx.initFiles(pkgPath, files, pkgName == "C")
|
||||
|
||||
169
cl/rewrite_internal_test.go
Normal file
169
cl/rewrite_internal_test.go
Normal file
@@ -0,0 +1,169 @@
|
||||
//go:build !llgo
|
||||
// +build !llgo
|
||||
|
||||
package cl
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
gpackages "github.com/goplus/gogen/packages"
|
||||
llssa "github.com/goplus/llgo/ssa"
|
||||
"github.com/goplus/llgo/ssa/ssatest"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
)
|
||||
|
||||
func init() {
|
||||
llssa.Initialize(llssa.InitAll | llssa.InitNative)
|
||||
}
|
||||
|
||||
func compileWithRewrites(t *testing.T, src string, rewrites map[string]string) string {
|
||||
t.Helper()
|
||||
fset := token.NewFileSet()
|
||||
file, err := parser.ParseFile(fset, "rewrite.go", src, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("parse failed: %v", err)
|
||||
}
|
||||
importer := gpackages.NewImporter(fset)
|
||||
mode := ssa.SanityCheckFunctions | ssa.InstantiateGenerics
|
||||
pkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer}, fset,
|
||||
types.NewPackage(file.Name.Name, file.Name.Name), []*ast.File{file}, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("build package failed: %v", err)
|
||||
}
|
||||
prog := ssatest.NewProgramEx(t, nil, importer)
|
||||
prog.TypeSizes(types.SizesFor("gc", runtime.GOARCH))
|
||||
ret, _, err := NewPackageEx(prog, nil, rewrites, pkg, []*ast.File{file})
|
||||
if err != nil {
|
||||
t.Fatalf("NewPackageEx failed: %v", err)
|
||||
}
|
||||
return ret.String()
|
||||
}
|
||||
|
||||
func TestRewriteGlobalStrings(t *testing.T) {
|
||||
const src = `package rewritepkg
|
||||
var VarInit = "original_value"
|
||||
var VarPlain string
|
||||
func Use() string { return VarInit + VarPlain }
|
||||
`
|
||||
ir := compileWithRewrites(t, src, map[string]string{
|
||||
"VarInit": "rewrite_init",
|
||||
"VarPlain": "rewrite_plain",
|
||||
})
|
||||
if strings.Contains(ir, "original_value") {
|
||||
t.Fatalf("original initializer still present:\n%s", ir)
|
||||
}
|
||||
for _, want := range []string{`c"rewrite_init"`, `c"rewrite_plain"`} {
|
||||
if !strings.Contains(ir, want) {
|
||||
t.Fatalf("missing %s in IR:\n%s", want, ir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRewriteSkipsNonConstStores(t *testing.T) {
|
||||
const src = `package rewritepkg
|
||||
import "strings"
|
||||
var VarInit = strings.ToUpper("original_value")
|
||||
var VarPlain string
|
||||
func Use() string { return VarInit + VarPlain }
|
||||
`
|
||||
ir := compileWithRewrites(t, src, map[string]string{
|
||||
"VarInit": "rewrite_init",
|
||||
"VarPlain": "rewrite_plain",
|
||||
})
|
||||
if !strings.Contains(ir, `c"rewrite_init"`) {
|
||||
t.Fatalf("expected rewrite_init constant to remain:\n%s", ir)
|
||||
}
|
||||
if !strings.Contains(ir, "strings.ToUpper") {
|
||||
t.Fatalf("expected call to strings.ToUpper in IR:\n%s", ir)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRewriteValueNoDot(t *testing.T) {
|
||||
ctx := &context{rewrites: map[string]string{"VarInit": "rewrite_init"}}
|
||||
if _, ok := ctx.rewriteValue("VarInit"); ok {
|
||||
t.Fatalf("rewriteValue should skip names without package prefix")
|
||||
}
|
||||
if _, ok := ctx.rewriteValue("pkg."); ok {
|
||||
t.Fatalf("rewriteValue should skip trailing dot names")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsStringPtrTypeDefault(t *testing.T) {
|
||||
ctx := &context{}
|
||||
if ctx.isStringPtrType(types.NewPointer(types.Typ[types.Int])) {
|
||||
t.Fatalf("expected non-string pointer to return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsStringPtrTypeBranches(t *testing.T) {
|
||||
ctx := &context{}
|
||||
if ctx.isStringPtrType(types.NewSlice(types.Typ[types.String])) {
|
||||
t.Fatalf("slice should trigger default branch and return false")
|
||||
}
|
||||
if ctx.isStringPtrType(nil) {
|
||||
t.Fatalf("nil type should return false")
|
||||
}
|
||||
if !ctx.isStringPtrType(types.NewPointer(types.Typ[types.String])) {
|
||||
t.Fatalf("*string should return true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRewriteIgnoredInNonInitStore(t *testing.T) {
|
||||
const src = `package rewritepkg
|
||||
var VarInit = "original_value"
|
||||
func Override() { VarInit = "override_value" }
|
||||
`
|
||||
ir := compileWithRewrites(t, src, map[string]string{"VarInit": "rewrite_init"})
|
||||
if !strings.Contains(ir, `c"override_value"`) {
|
||||
t.Fatalf("override store should retain original literal:\n%s", ir)
|
||||
}
|
||||
if !strings.Contains(ir, `c"rewrite_init"`) {
|
||||
t.Fatalf("global initializer should still be rewritten:\n%s", ir)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRewriteMissingEntry(t *testing.T) {
|
||||
const src = `package rewritepkg
|
||||
var VarInit = "original_value"
|
||||
var VarOther = "other_value"
|
||||
`
|
||||
ir := compileWithRewrites(t, src, map[string]string{"VarInit": "rewrite_init"})
|
||||
if !strings.Contains(ir, `c"other_value"`) {
|
||||
t.Fatalf("VarOther should keep original initializer:\n%s", ir)
|
||||
}
|
||||
if !strings.Contains(ir, `c"rewrite_init"`) {
|
||||
t.Fatalf("VarInit should still be rewritten:\n%s", ir)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRewriteIgnoresNonStringVar(t *testing.T) {
|
||||
const src = `package rewritepkg
|
||||
type wrapper struct{ v int }
|
||||
var VarStruct = wrapper{v: 1}
|
||||
`
|
||||
ir := compileWithRewrites(t, src, map[string]string{"VarStruct": "rewrite_struct"})
|
||||
if strings.Contains(ir, `c"rewrite_struct"`) {
|
||||
t.Fatalf("non-string variables must not be rewritten:\n%s", ir)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRewriteIgnoresStringAlias(t *testing.T) {
|
||||
const src = `package rewritepkg
|
||||
type T string
|
||||
var VarAlias T = "original_value"
|
||||
`
|
||||
ir := compileWithRewrites(t, src, map[string]string{"VarAlias": "rewrite_alias"})
|
||||
if strings.Contains(ir, `c"rewrite_alias"`) {
|
||||
t.Fatalf("string alias types must not be rewritten:\n%s", ir)
|
||||
}
|
||||
if !strings.Contains(ir, `c"original_value"`) {
|
||||
t.Fatalf("original value should remain for alias type:\n%s", ir)
|
||||
}
|
||||
}
|
||||
@@ -132,10 +132,16 @@ type Config struct {
|
||||
CheckLinkArgs bool // check linkargs valid
|
||||
ForceEspClang bool // force to use esp-clang
|
||||
Tags string
|
||||
GlobalNames map[string][]string // pkg => names
|
||||
GlobalDatas map[string]string // pkg.name => data
|
||||
// GlobalRewrites specifies compile-time overrides for global string variables.
|
||||
// Keys are fully qualified package paths (e.g. "main" or "github.com/user/pkg").
|
||||
// Each Rewrites entry maps variable names to replacement string values. Only
|
||||
// string-typed globals are supported and "main" applies to all root main
|
||||
// packages in the current build.
|
||||
GlobalRewrites map[string]Rewrites
|
||||
}
|
||||
|
||||
type Rewrites map[string]string
|
||||
|
||||
func NewDefaultConf(mode Mode) *Config {
|
||||
bin := os.Getenv("GOBIN")
|
||||
if bin == "" {
|
||||
@@ -335,6 +341,10 @@ func Do(args []string, conf *Config) ([]Package, error) {
|
||||
crossCompile: export,
|
||||
cTransformer: cabi.NewTransformer(prog, export.LLVMTarget, export.TargetABI, conf.AbiMode, cabiOptimize),
|
||||
}
|
||||
|
||||
// default runtime globals must be registered before packages are built
|
||||
addGlobalString(conf, "runtime.defaultGOROOT="+runtime.GOROOT(), nil)
|
||||
addGlobalString(conf, "runtime.buildVersion="+runtime.Version(), nil)
|
||||
pkgs, err := buildAllPkgs(ctx, initial, verbose)
|
||||
check(err)
|
||||
if mode == ModeGen {
|
||||
@@ -345,19 +355,11 @@ func Do(args []string, conf *Config) ([]Package, error) {
|
||||
}
|
||||
return nil, fmt.Errorf("initial package not found")
|
||||
}
|
||||
|
||||
dpkg, err := buildAllPkgs(ctx, altPkgs[noRt:], verbose)
|
||||
check(err)
|
||||
allPkgs := append([]*aPackage{}, pkgs...)
|
||||
allPkgs = append(allPkgs, dpkg...)
|
||||
|
||||
// update globals importpath.name=value
|
||||
addGlobalString(conf, "runtime.defaultGOROOT="+runtime.GOROOT(), nil)
|
||||
addGlobalString(conf, "runtime.buildVersion="+runtime.Version(), nil)
|
||||
|
||||
global, err := createGlobals(ctx, ctx.prog, pkgs)
|
||||
check(err)
|
||||
|
||||
for _, pkg := range initial {
|
||||
if needLink(pkg, mode) {
|
||||
name := path.Base(pkg.PkgPath)
|
||||
@@ -369,7 +371,7 @@ func Do(args []string, conf *Config) ([]Package, error) {
|
||||
}
|
||||
|
||||
// Link main package using the output path from buildOutFmts
|
||||
err = linkMainPkg(ctx, pkg, allPkgs, global, outFmts.Out, verbose)
|
||||
err = linkMainPkg(ctx, pkg, allPkgs, outFmts.Out, verbose)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -629,58 +631,59 @@ var (
|
||||
errXflags = errors.New("-X flag requires argument of the form importpath.name=value")
|
||||
)
|
||||
|
||||
// maxRewriteValueLength limits the size of rewrite values to prevent
|
||||
// excessive memory usage and potential resource exhaustion during compilation.
|
||||
const maxRewriteValueLength = 1 << 20 // 1 MiB cap per rewrite value
|
||||
|
||||
func addGlobalString(conf *Config, arg string, mainPkgs []string) {
|
||||
addGlobalStringWith(conf, arg, mainPkgs, true)
|
||||
}
|
||||
|
||||
func addGlobalStringWith(conf *Config, arg string, mainPkgs []string, skipIfExists bool) {
|
||||
eq := strings.Index(arg, "=")
|
||||
dot := strings.LastIndex(arg[:eq+1], ".")
|
||||
if eq < 0 || dot < 0 {
|
||||
panic(errXflags)
|
||||
}
|
||||
pkg := arg[:dot]
|
||||
varName := arg[dot+1 : eq]
|
||||
value := arg[eq+1:]
|
||||
validateRewriteInput(pkg, varName, value)
|
||||
pkgs := []string{pkg}
|
||||
if pkg == "main" {
|
||||
pkgs = mainPkgs
|
||||
}
|
||||
if conf.GlobalNames == nil {
|
||||
conf.GlobalNames = make(map[string][]string)
|
||||
if len(pkgs) == 0 {
|
||||
return
|
||||
}
|
||||
if conf.GlobalDatas == nil {
|
||||
conf.GlobalDatas = make(map[string]string)
|
||||
if conf.GlobalRewrites == nil {
|
||||
conf.GlobalRewrites = make(map[string]Rewrites)
|
||||
}
|
||||
for _, pkg := range pkgs {
|
||||
name := pkg + arg[dot:eq]
|
||||
value := arg[eq+1:]
|
||||
if _, ok := conf.GlobalDatas[name]; !ok {
|
||||
conf.GlobalNames[pkg] = append(conf.GlobalNames[pkg], name)
|
||||
for _, realPkg := range pkgs {
|
||||
vars := conf.GlobalRewrites[realPkg]
|
||||
if vars == nil {
|
||||
vars = make(Rewrites)
|
||||
conf.GlobalRewrites[realPkg] = vars
|
||||
}
|
||||
conf.GlobalDatas[name] = value
|
||||
if skipIfExists {
|
||||
if _, exists := vars[varName]; exists {
|
||||
continue
|
||||
}
|
||||
}
|
||||
vars[varName] = value
|
||||
}
|
||||
}
|
||||
|
||||
func createGlobals(ctx *context, prog llssa.Program, pkgs []*aPackage) (llssa.Package, error) {
|
||||
if len(ctx.buildConf.GlobalDatas) == 0 {
|
||||
return nil, nil
|
||||
func validateRewriteInput(pkg, varName, value string) {
|
||||
if pkg == "" || strings.ContainsAny(pkg, " \t\r\n") {
|
||||
panic(fmt.Errorf("invalid package path for rewrite: %q", pkg))
|
||||
}
|
||||
for _, pkg := range pkgs {
|
||||
if pkg.ExportFile == "" {
|
||||
continue
|
||||
}
|
||||
if names, ok := ctx.buildConf.GlobalNames[pkg.PkgPath]; ok {
|
||||
err := pkg.LPkg.Undefined(names...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkg.ExportFile += "-global"
|
||||
pkg.ExportFile, err = exportObject(ctx, pkg.PkgPath+".global", pkg.ExportFile, []byte(pkg.LPkg.String()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !token.IsIdentifier(varName) {
|
||||
panic(fmt.Errorf("invalid variable name for rewrite: %q", varName))
|
||||
}
|
||||
global := prog.NewPackage("", "global")
|
||||
for name, value := range ctx.buildConf.GlobalDatas {
|
||||
global.AddGlobalString(name, value)
|
||||
if len(value) > maxRewriteValueLength {
|
||||
panic(fmt.Errorf("rewrite value too large: %d bytes", len(value)))
|
||||
}
|
||||
return global, nil
|
||||
}
|
||||
|
||||
// compileExtraFiles compiles extra files (.s/.c) from target configuration and returns object files
|
||||
@@ -752,7 +755,7 @@ func compileExtraFiles(ctx *context, verbose bool) ([]string, error) {
|
||||
return objFiles, nil
|
||||
}
|
||||
|
||||
func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global llssa.Package, outputPath string, verbose bool) error {
|
||||
func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, outputPath string, verbose bool) error {
|
||||
|
||||
needRuntime := false
|
||||
needPyInit := false
|
||||
@@ -769,7 +772,6 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
|
||||
if p.ExportFile != "" && aPkg != nil { // skip packages that only contain declarations
|
||||
linkArgs = append(linkArgs, aPkg.LinkArgs...)
|
||||
objFiles = append(objFiles, aPkg.LLFiles...)
|
||||
objFiles = append(objFiles, aPkg.ExportFile)
|
||||
need1, need2 := isNeedRuntimeOrPyInit(ctx, p)
|
||||
if !needRuntime {
|
||||
needRuntime = need1
|
||||
@@ -794,14 +796,6 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
|
||||
}
|
||||
objFiles = append(objFiles, extraObjFiles...)
|
||||
|
||||
if global != nil {
|
||||
export, err := exportObject(ctx, pkg.PkgPath+".global", pkg.ExportFile+"-global", []byte(global.String()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
objFiles = append(objFiles, export)
|
||||
}
|
||||
|
||||
if IsFullRpathEnabled() {
|
||||
// Treat every link-time library search path, specified by the -L parameter, as a runtime search path as well.
|
||||
// This is to ensure the final executable can locate libraries with a relocatable install_name
|
||||
@@ -1050,7 +1044,7 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error {
|
||||
cl.SetDebug(cl.DbgFlagAll)
|
||||
}
|
||||
|
||||
ret, externs, err := cl.NewPackageEx(ctx.prog, ctx.patches, aPkg.SSA, syntax)
|
||||
ret, externs, err := cl.NewPackageEx(ctx.prog, ctx.patches, aPkg.rewriteVars, aPkg.SSA, syntax)
|
||||
if showDetail {
|
||||
llssa.SetDebug(0)
|
||||
cl.SetDebug(0)
|
||||
@@ -1077,10 +1071,11 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error {
|
||||
aPkg.LinkArgs = append(aPkg.LinkArgs, altLdflags...)
|
||||
}
|
||||
if pkg.ExportFile != "" {
|
||||
pkg.ExportFile, err = exportObject(ctx, pkg.PkgPath, pkg.ExportFile, []byte(ret.String()))
|
||||
exportFile, err := exportObject(ctx, pkg.PkgPath, pkg.ExportFile, []byte(ret.String()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("export object of %v failed: %v", pkgPath, err)
|
||||
}
|
||||
aPkg.LLFiles = append(aPkg.LLFiles, exportFile)
|
||||
if debugBuild || verbose {
|
||||
fmt.Fprintf(os.Stderr, "==> Export %s: %s\n", aPkg.PkgPath, pkg.ExportFile)
|
||||
}
|
||||
@@ -1089,11 +1084,15 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error {
|
||||
}
|
||||
|
||||
func exportObject(ctx *context, pkgPath string, exportFile string, data []byte) (string, error) {
|
||||
f, err := os.CreateTemp("", "llgo-*.ll")
|
||||
base := filepath.Base(exportFile)
|
||||
f, err := os.CreateTemp("", base+"-*.ll")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
f.Write(data)
|
||||
if _, err := f.Write(data); err != nil {
|
||||
f.Close()
|
||||
return "", err
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return exportFile, err
|
||||
@@ -1111,13 +1110,17 @@ func exportObject(ctx *context, pkgPath string, exportFile string, data []byte)
|
||||
}
|
||||
return exportFile, os.Rename(f.Name(), exportFile)
|
||||
}
|
||||
exportFile += ".o"
|
||||
args := []string{"-o", exportFile, "-c", f.Name(), "-Wno-override-module"}
|
||||
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.buildConf.Verbose {
|
||||
fmt.Fprintln(os.Stderr, "clang", args)
|
||||
}
|
||||
cmd := ctx.compiler()
|
||||
return exportFile, cmd.Compile(args...)
|
||||
return objFile.Name(), cmd.Compile(args...)
|
||||
}
|
||||
|
||||
func llcCheck(env *llvm.Env, exportFile string) (msg string, err error) {
|
||||
@@ -1171,8 +1174,9 @@ type aPackage struct {
|
||||
AltPkg *packages.Cached
|
||||
LPkg llssa.Package
|
||||
|
||||
LinkArgs []string
|
||||
LLFiles []string
|
||||
LinkArgs []string
|
||||
LLFiles []string
|
||||
rewriteVars map[string]string
|
||||
}
|
||||
|
||||
type Package = *aPackage
|
||||
@@ -1193,7 +1197,8 @@ func allPkgs(ctx *context, initial []*packages.Package, verbose bool) (all []*aP
|
||||
return
|
||||
}
|
||||
}
|
||||
all = append(all, &aPackage{p, ssaPkg, altPkg, nil, nil, nil})
|
||||
rewrites := collectRewriteVars(ctx, pkgPath)
|
||||
all = append(all, &aPackage{p, ssaPkg, altPkg, nil, nil, nil, rewrites})
|
||||
} else {
|
||||
errs = append(errs, p)
|
||||
}
|
||||
@@ -1201,6 +1206,32 @@ func allPkgs(ctx *context, initial []*packages.Package, verbose bool) (all []*aP
|
||||
return
|
||||
}
|
||||
|
||||
func collectRewriteVars(ctx *context, pkgPath string) map[string]string {
|
||||
data := ctx.buildConf.GlobalRewrites
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
basePath := strings.TrimPrefix(pkgPath, altPkgPathPrefix)
|
||||
if vars := data[basePath]; vars != nil {
|
||||
return cloneRewrites(vars)
|
||||
}
|
||||
if vars := data[pkgPath]; vars != nil {
|
||||
return cloneRewrites(vars)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cloneRewrites(src Rewrites) map[string]string {
|
||||
if len(src) == 0 {
|
||||
return nil
|
||||
}
|
||||
dup := make(map[string]string, len(src))
|
||||
for k, v := range src {
|
||||
dup[k] = v
|
||||
}
|
||||
return dup
|
||||
}
|
||||
|
||||
func createSSAPkg(prog *ssa.Program, p *packages.Package, verbose bool) *ssa.Package {
|
||||
pkgSSA := prog.ImportedPackage(p.ID)
|
||||
if pkgSSA == nil {
|
||||
|
||||
@@ -8,6 +8,9 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/goplus/llgo/internal/mockable"
|
||||
@@ -94,4 +97,65 @@ func TestCmpTest(t *testing.T) {
|
||||
mockRun([]string{"../../cl/_testgo/runtest"}, &Config{Mode: ModeCmpTest})
|
||||
}
|
||||
|
||||
// TestGenerateOutputFilenames removed - functionality moved to filename_test.go
|
||||
const (
|
||||
rewriteMainPkg = "github.com/goplus/llgo/cl/_testgo/rewrite"
|
||||
rewriteDepPkg = rewriteMainPkg + "/dep"
|
||||
rewriteDirPath = "../../cl/_testgo/rewrite"
|
||||
)
|
||||
|
||||
func TestLdFlagsRewriteVars(t *testing.T) {
|
||||
buildRewriteBinary(t, false, "build-main", "build-pkg")
|
||||
buildRewriteBinary(t, false, "rerun-main", "rerun-pkg")
|
||||
}
|
||||
|
||||
func TestLdFlagsRewriteVarsMainAlias(t *testing.T) {
|
||||
buildRewriteBinary(t, true, "alias-main", "alias-pkg")
|
||||
}
|
||||
|
||||
func buildRewriteBinary(t *testing.T, useMainAlias bool, mainVal, depVal string) {
|
||||
t.Helper()
|
||||
binPath := filepath.Join(t.TempDir(), "rewrite")
|
||||
if runtime.GOOS == "windows" {
|
||||
binPath += ".exe"
|
||||
}
|
||||
|
||||
cfg := &Config{Mode: ModeBuild, OutFile: binPath}
|
||||
mainKey := rewriteMainPkg
|
||||
var mainPkgs []string
|
||||
if useMainAlias {
|
||||
mainKey = "main"
|
||||
mainPkgs = []string{rewriteMainPkg}
|
||||
}
|
||||
mainPlain := mainVal + "-plain"
|
||||
depPlain := depVal + "-plain"
|
||||
gorootVal := "goroot-" + mainVal
|
||||
versionVal := "version-" + mainVal
|
||||
addGlobalString(cfg, mainKey+".VarName="+mainVal, mainPkgs)
|
||||
addGlobalString(cfg, mainKey+".VarPlain="+mainPlain, mainPkgs)
|
||||
addGlobalString(cfg, rewriteDepPkg+".VarName="+depVal, nil)
|
||||
addGlobalString(cfg, rewriteDepPkg+".VarPlain="+depPlain, nil)
|
||||
addGlobalString(cfg, "runtime.defaultGOROOT="+gorootVal, nil)
|
||||
addGlobalString(cfg, "runtime.buildVersion="+versionVal, nil)
|
||||
|
||||
if _, err := Do([]string{rewriteDirPath}, cfg); err != nil {
|
||||
t.Fatalf("ModeBuild failed: %v", err)
|
||||
}
|
||||
got := runBinary(t, binPath)
|
||||
want := fmt.Sprintf(
|
||||
"main.VarName: %s\nmain.VarPlain: %s\ndep.VarName: %s\ndep.VarPlain: %s\nruntime.GOROOT(): %s\nruntime.Version(): %s\n",
|
||||
mainVal, mainPlain, depVal, depPlain, gorootVal, versionVal,
|
||||
)
|
||||
if got != want {
|
||||
t.Fatalf("unexpected binary output:\nwant %q\ngot %q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func runBinary(t *testing.T, path string, args ...string) string {
|
||||
t.Helper()
|
||||
cmd := exec.Command(path, args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to run %s: %v\n%s", path, err, output)
|
||||
}
|
||||
return string(output)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,19 @@ func (pkg Package) AddGlobalString(name string, value string) {
|
||||
pkg.NewVarEx(name, prog.Pointer(styp)).Init(Expr{cv, styp})
|
||||
}
|
||||
|
||||
// ConstString creates an SSA expression representing a Go string literal. The
|
||||
// returned value is backed by an anonymous global constant and can be used to
|
||||
// initialize package-level variables or other constant contexts that expect a
|
||||
// Go string value.
|
||||
func (pkg Package) ConstString(value string) Expr {
|
||||
prog := pkg.Prog
|
||||
styp := prog.String()
|
||||
data := pkg.createGlobalStr(value)
|
||||
length := prog.IntVal(uint64(len(value)), prog.Uintptr())
|
||||
cv := llvm.ConstNamedStruct(styp.ll, []llvm.Value{data, length.impl})
|
||||
return Expr{cv, styp}
|
||||
}
|
||||
|
||||
// Undefined global string var by names
|
||||
func (pkg Package) Undefined(names ...string) error {
|
||||
prog := pkg.Prog
|
||||
|
||||
Reference in New Issue
Block a user