Skip to content

Commit 2d80951

Browse files
authored
Merge pull request #1396 from goplus/pr-1395
build: fix ldflags rewrites and prep caching
2 parents 2f65c98 + 034b05c commit 2d80951

8 files changed

Lines changed: 458 additions & 67 deletions

File tree

cl/_testgo/rewrite/dep/dep.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package dep
2+
3+
import "fmt"
4+
5+
var VarName = "dep-default"
6+
var VarPlain string
7+
8+
func PrintVar() {
9+
fmt.Printf("dep.VarName: %s\n", VarName)
10+
fmt.Printf("dep.VarPlain: %s\n", VarPlain)
11+
}

cl/_testgo/rewrite/main.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"runtime"
6+
7+
dep "github.com/goplus/llgo/cl/_testgo/rewrite/dep"
8+
)
9+
10+
var VarName = "main-default"
11+
var VarPlain string
12+
13+
func printLine(label, value string) {
14+
fmt.Printf("%s: %s\n", label, value)
15+
}
16+
17+
func main() {
18+
printLine("main.VarName", VarName)
19+
printLine("main.VarPlain", VarPlain)
20+
dep.PrintVar()
21+
printLine("runtime.GOROOT()", runtime.GOROOT())
22+
printLine("runtime.Version()", runtime.Version())
23+
}

cl/_testgo/rewrite/out.ll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
;

cl/compile.go

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,58 @@ type context struct {
127127
cgoArgs []llssa.Expr
128128
cgoRet llssa.Expr
129129
cgoSymbols []string
130+
rewrites map[string]string
131+
}
132+
133+
func (p *context) rewriteValue(name string) (string, bool) {
134+
if p.rewrites == nil {
135+
return "", false
136+
}
137+
dot := strings.LastIndex(name, ".")
138+
if dot < 0 || dot == len(name)-1 {
139+
return "", false
140+
}
141+
varName := name[dot+1:]
142+
val, ok := p.rewrites[varName]
143+
return val, ok
144+
}
145+
146+
// isStringPtrType checks if typ is a pointer to the basic string type (*string).
147+
// This is used to validate that -ldflags -X can only rewrite variables of type *string,
148+
// not derived string types like "type T string".
149+
func (p *context) isStringPtrType(typ types.Type) bool {
150+
ptr, ok := typ.(*types.Pointer)
151+
if !ok {
152+
return false
153+
}
154+
basic, ok := ptr.Elem().(*types.Basic)
155+
return ok && basic.Kind() == types.String
156+
}
157+
158+
func (p *context) globalFullName(g *ssa.Global) string {
159+
name, _, _ := p.varName(g.Pkg.Pkg, g)
160+
return name
161+
}
162+
163+
func (p *context) rewriteInitStore(store *ssa.Store, g *ssa.Global) (string, bool) {
164+
if p.rewrites == nil {
165+
return "", false
166+
}
167+
fn := store.Block().Parent()
168+
if fn == nil || fn.Synthetic != "package initializer" {
169+
return "", false
170+
}
171+
if _, ok := store.Val.(*ssa.Const); !ok {
172+
return "", false
173+
}
174+
if !p.isStringPtrType(g.Type()) {
175+
return "", false
176+
}
177+
value, ok := p.rewriteValue(p.globalFullName(g))
178+
if !ok {
179+
return "", false
180+
}
181+
return value, true
130182
}
131183

132184
type pkgState byte
@@ -176,7 +228,16 @@ func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) {
176228
log.Println("==> NewVar", name, typ)
177229
}
178230
g := pkg.NewVar(name, typ, llssa.Background(vtype))
179-
if define {
231+
if value, ok := p.rewriteValue(name); ok {
232+
if p.isStringPtrType(gbl.Type()) {
233+
g.Init(pkg.ConstString(value))
234+
} else {
235+
log.Printf("warning: ignoring rewrite for non-string variable %s (type: %v)", name, gbl.Type())
236+
if define {
237+
g.InitNil()
238+
}
239+
}
240+
} else if define {
180241
g.InitNil()
181242
}
182243
}
@@ -816,6 +877,13 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) {
816877
return
817878
}
818879
}
880+
if p.rewrites != nil {
881+
if g, ok := va.(*ssa.Global); ok {
882+
if _, ok := p.rewriteInitStore(v, g); ok {
883+
return
884+
}
885+
}
886+
}
819887
ptr := p.compileValue(b, va)
820888
val := p.compileValue(b, v.Val)
821889
b.Store(ptr, val)
@@ -980,12 +1048,22 @@ type Patches = map[string]Patch
9801048

9811049
// NewPackage compiles a Go package to LLVM IR package.
9821050
func NewPackage(prog llssa.Program, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, err error) {
983-
ret, _, err = NewPackageEx(prog, nil, pkg, files)
1051+
ret, _, err = NewPackageEx(prog, nil, nil, pkg, files)
9841052
return
9851053
}
9861054

9871055
// NewPackageEx compiles a Go package to LLVM IR package.
988-
func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, externs []string, err error) {
1056+
//
1057+
// Parameters:
1058+
// - prog: target LLVM SSA program context
1059+
// - patches: optional package patches applied during compilation
1060+
// - rewrites: per-package string initializers rewritten at compile time
1061+
// - pkg: SSA package to compile
1062+
// - files: parsed AST files that belong to the package
1063+
//
1064+
// The rewrites map uses short variable names (without package qualifier) and
1065+
// only affects string-typed globals defined in the current package.
1066+
func NewPackageEx(prog llssa.Program, patches Patches, rewrites map[string]string, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, externs []string, err error) {
9891067
pkgProg := pkg.Prog
9901068
pkgTypes := pkg.Pkg
9911069
oldTypes := pkgTypes
@@ -1018,6 +1096,7 @@ func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files [
10181096
types.Unsafe: {kind: PkgDeclOnly}, // TODO(xsw): PkgNoInit or PkgDeclOnly?
10191097
},
10201098
cgoSymbols: make([]string, 0, 128),
1099+
rewrites: rewrites,
10211100
}
10221101
ctx.initPyModule()
10231102
ctx.initFiles(pkgPath, files, pkgName == "C")

cl/rewrite_internal_test.go

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
//go:build !llgo
2+
// +build !llgo
3+
4+
package cl
5+
6+
import (
7+
"go/ast"
8+
"go/parser"
9+
"go/token"
10+
"go/types"
11+
"runtime"
12+
"strings"
13+
"testing"
14+
15+
gpackages "github.com/goplus/gogen/packages"
16+
llssa "github.com/goplus/llgo/ssa"
17+
"github.com/goplus/llgo/ssa/ssatest"
18+
"golang.org/x/tools/go/ssa"
19+
"golang.org/x/tools/go/ssa/ssautil"
20+
)
21+
22+
func init() {
23+
llssa.Initialize(llssa.InitAll | llssa.InitNative)
24+
}
25+
26+
func compileWithRewrites(t *testing.T, src string, rewrites map[string]string) string {
27+
t.Helper()
28+
fset := token.NewFileSet()
29+
file, err := parser.ParseFile(fset, "rewrite.go", src, 0)
30+
if err != nil {
31+
t.Fatalf("parse failed: %v", err)
32+
}
33+
importer := gpackages.NewImporter(fset)
34+
mode := ssa.SanityCheckFunctions | ssa.InstantiateGenerics
35+
pkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer}, fset,
36+
types.NewPackage(file.Name.Name, file.Name.Name), []*ast.File{file}, mode)
37+
if err != nil {
38+
t.Fatalf("build package failed: %v", err)
39+
}
40+
prog := ssatest.NewProgramEx(t, nil, importer)
41+
prog.TypeSizes(types.SizesFor("gc", runtime.GOARCH))
42+
ret, _, err := NewPackageEx(prog, nil, rewrites, pkg, []*ast.File{file})
43+
if err != nil {
44+
t.Fatalf("NewPackageEx failed: %v", err)
45+
}
46+
return ret.String()
47+
}
48+
49+
func TestRewriteGlobalStrings(t *testing.T) {
50+
const src = `package rewritepkg
51+
var VarInit = "original_value"
52+
var VarPlain string
53+
func Use() string { return VarInit + VarPlain }
54+
`
55+
ir := compileWithRewrites(t, src, map[string]string{
56+
"VarInit": "rewrite_init",
57+
"VarPlain": "rewrite_plain",
58+
})
59+
if strings.Contains(ir, "original_value") {
60+
t.Fatalf("original initializer still present:\n%s", ir)
61+
}
62+
for _, want := range []string{`c"rewrite_init"`, `c"rewrite_plain"`} {
63+
if !strings.Contains(ir, want) {
64+
t.Fatalf("missing %s in IR:\n%s", want, ir)
65+
}
66+
}
67+
}
68+
69+
func TestRewriteSkipsNonConstStores(t *testing.T) {
70+
const src = `package rewritepkg
71+
import "strings"
72+
var VarInit = strings.ToUpper("original_value")
73+
var VarPlain string
74+
func Use() string { return VarInit + VarPlain }
75+
`
76+
ir := compileWithRewrites(t, src, map[string]string{
77+
"VarInit": "rewrite_init",
78+
"VarPlain": "rewrite_plain",
79+
})
80+
if !strings.Contains(ir, `c"rewrite_init"`) {
81+
t.Fatalf("expected rewrite_init constant to remain:\n%s", ir)
82+
}
83+
if !strings.Contains(ir, "strings.ToUpper") {
84+
t.Fatalf("expected call to strings.ToUpper in IR:\n%s", ir)
85+
}
86+
}
87+
88+
func TestRewriteValueNoDot(t *testing.T) {
89+
ctx := &context{rewrites: map[string]string{"VarInit": "rewrite_init"}}
90+
if _, ok := ctx.rewriteValue("VarInit"); ok {
91+
t.Fatalf("rewriteValue should skip names without package prefix")
92+
}
93+
if _, ok := ctx.rewriteValue("pkg."); ok {
94+
t.Fatalf("rewriteValue should skip trailing dot names")
95+
}
96+
}
97+
98+
func TestIsStringPtrTypeDefault(t *testing.T) {
99+
ctx := &context{}
100+
if ctx.isStringPtrType(types.NewPointer(types.Typ[types.Int])) {
101+
t.Fatalf("expected non-string pointer to return false")
102+
}
103+
}
104+
105+
func TestIsStringPtrTypeBranches(t *testing.T) {
106+
ctx := &context{}
107+
if ctx.isStringPtrType(types.NewSlice(types.Typ[types.String])) {
108+
t.Fatalf("slice should trigger default branch and return false")
109+
}
110+
if ctx.isStringPtrType(nil) {
111+
t.Fatalf("nil type should return false")
112+
}
113+
if !ctx.isStringPtrType(types.NewPointer(types.Typ[types.String])) {
114+
t.Fatalf("*string should return true")
115+
}
116+
}
117+
118+
func TestRewriteIgnoredInNonInitStore(t *testing.T) {
119+
const src = `package rewritepkg
120+
var VarInit = "original_value"
121+
func Override() { VarInit = "override_value" }
122+
`
123+
ir := compileWithRewrites(t, src, map[string]string{"VarInit": "rewrite_init"})
124+
if !strings.Contains(ir, `c"override_value"`) {
125+
t.Fatalf("override store should retain original literal:\n%s", ir)
126+
}
127+
if !strings.Contains(ir, `c"rewrite_init"`) {
128+
t.Fatalf("global initializer should still be rewritten:\n%s", ir)
129+
}
130+
}
131+
132+
func TestRewriteMissingEntry(t *testing.T) {
133+
const src = `package rewritepkg
134+
var VarInit = "original_value"
135+
var VarOther = "other_value"
136+
`
137+
ir := compileWithRewrites(t, src, map[string]string{"VarInit": "rewrite_init"})
138+
if !strings.Contains(ir, `c"other_value"`) {
139+
t.Fatalf("VarOther should keep original initializer:\n%s", ir)
140+
}
141+
if !strings.Contains(ir, `c"rewrite_init"`) {
142+
t.Fatalf("VarInit should still be rewritten:\n%s", ir)
143+
}
144+
}
145+
146+
func TestRewriteIgnoresNonStringVar(t *testing.T) {
147+
const src = `package rewritepkg
148+
type wrapper struct{ v int }
149+
var VarStruct = wrapper{v: 1}
150+
`
151+
ir := compileWithRewrites(t, src, map[string]string{"VarStruct": "rewrite_struct"})
152+
if strings.Contains(ir, `c"rewrite_struct"`) {
153+
t.Fatalf("non-string variables must not be rewritten:\n%s", ir)
154+
}
155+
}
156+
157+
func TestRewriteIgnoresStringAlias(t *testing.T) {
158+
const src = `package rewritepkg
159+
type T string
160+
var VarAlias T = "original_value"
161+
`
162+
ir := compileWithRewrites(t, src, map[string]string{"VarAlias": "rewrite_alias"})
163+
if strings.Contains(ir, `c"rewrite_alias"`) {
164+
t.Fatalf("string alias types must not be rewritten:\n%s", ir)
165+
}
166+
if !strings.Contains(ir, `c"original_value"`) {
167+
t.Fatalf("original value should remain for alias type:\n%s", ir)
168+
}
169+
}

0 commit comments

Comments
 (0)