Merge pull request #1396 from goplus/pr-1395

build: fix ldflags rewrites and prep caching
This commit is contained in:
xushiwei
2025-11-15 07:36:31 +08:00
committed by GitHub
8 changed files with 458 additions and 67 deletions

View 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)
}

View 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())
}

View File

@@ -0,0 +1 @@
;

View File

@@ -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
View 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)
}
}

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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