diff --git a/cl/_testgo/rewrite/dep/dep.go b/cl/_testgo/rewrite/dep/dep.go new file mode 100644 index 00000000..6dc2e879 --- /dev/null +++ b/cl/_testgo/rewrite/dep/dep.go @@ -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) +} diff --git a/cl/_testgo/rewrite/main.go b/cl/_testgo/rewrite/main.go new file mode 100644 index 00000000..4bd616e4 --- /dev/null +++ b/cl/_testgo/rewrite/main.go @@ -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()) +} diff --git a/cl/_testgo/rewrite/out.ll b/cl/_testgo/rewrite/out.ll new file mode 100644 index 00000000..1c8a0e79 --- /dev/null +++ b/cl/_testgo/rewrite/out.ll @@ -0,0 +1 @@ +; \ No newline at end of file diff --git a/cl/compile.go b/cl/compile.go index 082b34b5..d9381137 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -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") diff --git a/cl/rewrite_internal_test.go b/cl/rewrite_internal_test.go new file mode 100644 index 00000000..8fbf89a3 --- /dev/null +++ b/cl/rewrite_internal_test.go @@ -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) + } +} diff --git a/internal/build/build.go b/internal/build/build.go index 1147d495..2c7de4e4 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -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 { diff --git a/internal/build/build_test.go b/internal/build/build_test.go index ead07db0..c71f0d74 100644 --- a/internal/build/build_test.go +++ b/internal/build/build_test.go @@ -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) +} diff --git a/ssa/globals.go b/ssa/globals.go index fc15fb59..e3867003 100644 --- a/ssa/globals.go +++ b/ssa/globals.go @@ -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