diff --git a/internal/build/build.go b/internal/build/build.go index 44b01134..c918051d 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -19,6 +19,7 @@ package build import ( "bytes" "debug/macho" + "errors" "fmt" "go/ast" "go/constant" @@ -67,16 +68,18 @@ const ( ) type Config struct { - Goos string - Goarch string - 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 - GenExpect bool // only valid for ModeCmpTest - Verbose bool - Tags string + Goos string + Goarch string + 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 + GenExpect bool // only valid for ModeCmpTest + Verbose bool + Tags string + GlobalNames map[string][]string // pkg => names + GlobalDatas map[string]string // pkg.name => data } func NewDefaultConf(mode Mode) *Config { @@ -266,9 +269,16 @@ func Do(args []string, conf *Config) ([]Package, error) { 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(conf, ctx.prog, pkgs) + check(err) + for _, pkg := range initial { if needLink(pkg, mode) { - linkMainPkg(ctx, pkg, allPkgs, conf, mode, verbose) + linkMainPkg(ctx, pkg, allPkgs, global, conf, mode, verbose) } } @@ -429,7 +439,62 @@ func buildAllPkgs(ctx *context, initial []*packages.Package, verbose bool) (pkgs return } -func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, conf *Config, mode Mode, verbose bool) { +var ( + errXflags = errors.New("-X flag requires argument of the form importpath.name=value") +) + +func addGlobalString(conf *Config, arg string, mainPkgs []string) { + eq := strings.Index(arg, "=") + dot := strings.LastIndex(arg[:eq+1], ".") + if eq < 0 || dot < 0 { + panic(errXflags) + } + pkg := arg[:dot] + pkgs := []string{pkg} + if pkg == "main" { + pkgs = mainPkgs + } + if conf.GlobalNames == nil { + conf.GlobalNames = make(map[string][]string) + } + if conf.GlobalDatas == nil { + conf.GlobalDatas = make(map[string]string) + } + 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) + } + conf.GlobalDatas[name] = value + } +} + +func createGlobals(conf *Config, prog llssa.Program, pkgs []*aPackage) (llssa.Package, error) { + if len(conf.GlobalDatas) == 0 { + return nil, nil + } + for _, pkg := range pkgs { + if pkg.ExportFile == "" { + continue + } + if names, ok := conf.GlobalNames[pkg.PkgPath]; ok { + err := pkg.LPkg.Undefined(names...) + if err != nil { + return nil, err + } + pkg.ExportFile = pkg.ExportFile + "-global.ll" + os.WriteFile(pkg.ExportFile, []byte(pkg.LPkg.String()), 0644) + } + } + global := prog.NewPackage("", "global") + for name, value := range conf.GlobalDatas { + global.AddGlobalString(name, value) + } + return global, nil +} + +func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global llssa.Package, conf *Config, mode Mode, verbose bool) { pkgPath := pkg.PkgPath name := path.Base(pkgPath) app := conf.OutFile @@ -462,6 +527,7 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, conf *Co if p.ExportFile != "" && aPkg != nil { // skip packages that only contain declarations linkArgs = append(linkArgs, aPkg.LinkArgs...) llFiles = append(llFiles, aPkg.LLFiles...) + llFiles = append(llFiles, aPkg.ExportFile) need1, need2 := isNeedRuntimeOrPyInit(ctx, p) if !needRuntime { needRuntime = need1 @@ -476,6 +542,12 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, conf *Co // defer os.Remove(entryLLFile) llFiles = append(llFiles, entryLLFile) + if global != nil { + export := pkg.ExportFile + "-global.ll" + os.WriteFile(export, []byte(global.String()), 0666) + llFiles = append(llFiles, export) + } + // add rpath and find libs exargs := make([]string, 0, ctx.nLibdir<<1) libs := make([]string, 0, ctx.nLibdir*3) @@ -726,7 +798,6 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error { if pkg.ExportFile != "" { pkg.ExportFile += ".ll" os.WriteFile(pkg.ExportFile, []byte(ret.String()), 0644) - aPkg.LLFiles = append(aPkg.LLFiles, pkg.ExportFile) if debugBuild || verbose { fmt.Fprintf(os.Stderr, "==> Export %s: %s\n", aPkg.PkgPath, pkg.ExportFile) } diff --git a/runtime/internal/lib/runtime/runtime.go b/runtime/internal/lib/runtime/runtime.go index 398d248a..0b6fd9a7 100644 --- a/runtime/internal/lib/runtime/runtime.go +++ b/runtime/internal/lib/runtime/runtime.go @@ -31,11 +31,16 @@ const ( LLGoFiles = "_wrap/runtime.c" ) -// GOROOT returns the root of the Go tree. It uses the -// GOROOT environment variable, if set at process start, -// or else the root used during the Go build. +var defaultGOROOT string // set by cmd/link + func GOROOT() string { - return "" + return defaultGOROOT +} + +var buildVersion string + +func Version() string { + return buildVersion } //go:linkname c_maxprocs C.llgo_maxprocs diff --git a/ssa/expr.go b/ssa/expr.go index 29a4dd75..ffebb888 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -301,32 +301,11 @@ func (b Builder) CMalloc(n Expr) Expr { // Str returns a Go string constant expression. func (b Builder) Str(v string) Expr { prog := b.Prog - data := b.createGlobalStr(v) + data := b.Pkg.createGlobalStr(v) size := llvm.ConstInt(prog.tyInt(), uint64(len(v)), false) return Expr{aggregateValue(b.impl, prog.rtString(), data, size), prog.String()} } -func (b Builder) createGlobalStr(v string) (ret llvm.Value) { - if ret, ok := b.Pkg.strs[v]; ok { - return ret - } - prog := b.Prog - if v != "" { - typ := llvm.ArrayType(prog.tyInt8(), len(v)) - global := llvm.AddGlobal(b.Pkg.mod, typ, "") - global.SetInitializer(b.Prog.ctx.ConstString(v, false)) - global.SetLinkage(llvm.PrivateLinkage) - global.SetGlobalConstant(true) - global.SetUnnamedAddr(true) - global.SetAlignment(1) - ret = llvm.ConstInBoundsGEP(typ, global, []llvm.Value{prog.Val(0).impl}) - } else { - ret = llvm.ConstNull(prog.CStr().ll) - } - b.Pkg.strs[v] = ret - return -} - // unsafeString(data *byte, size int) string func (b Builder) unsafeString(data, size llvm.Value) Expr { prog := b.Prog diff --git a/ssa/globals.go b/ssa/globals.go new file mode 100644 index 00000000..fc15fb59 --- /dev/null +++ b/ssa/globals.go @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025 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 ssa + +import ( + "fmt" + + "github.com/goplus/llvm" +) + +func (pkg Package) AddGlobalString(name string, value string) { + 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}) + pkg.NewVarEx(name, prog.Pointer(styp)).Init(Expr{cv, styp}) +} + +// Undefined global string var by names +func (pkg Package) Undefined(names ...string) error { + prog := pkg.Prog + styp := prog.rtString() + for _, name := range names { + global := pkg.VarOf(name) + if global == nil { + continue + } + typ := prog.Elem(global.Type) + if typ.ll != styp { + return fmt.Errorf("%s: not a var of type string (type:%v)", name, typ.RawType()) + } + newGlobal := llvm.AddGlobal(pkg.mod, styp, "") + global.impl.ReplaceAllUsesWith(newGlobal) + global.impl.EraseFromParentAsGlobal() + newGlobal.SetName(name) + } + return nil +} diff --git a/ssa/package.go b/ssa/package.go index 981eebb6..7912f3ab 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -788,6 +788,27 @@ func (p Package) InitDebug(name, pkgPath string, positioner Positioner) { p.cu = p.di.createCompileUnit(name, pkgPath) } +func (p Package) createGlobalStr(v string) (ret llvm.Value) { + if ret, ok := p.strs[v]; ok { + return ret + } + prog := p.Prog + if v != "" { + typ := llvm.ArrayType(prog.tyInt8(), len(v)) + global := llvm.AddGlobal(p.mod, typ, "") + global.SetInitializer(prog.ctx.ConstString(v, false)) + global.SetLinkage(llvm.PrivateLinkage) + global.SetGlobalConstant(true) + global.SetUnnamedAddr(true) + global.SetAlignment(1) + ret = llvm.ConstInBoundsGEP(typ, global, []llvm.Value{prog.Val(0).impl}) + } else { + ret = llvm.ConstNull(prog.CStr().ll) + } + p.strs[v] = ret + return +} + // ----------------------------------------------------------------------------- /* diff --git a/ssa/ssa_test.go b/ssa/ssa_test.go index b61fb624..1757101a 100644 --- a/ssa/ssa_test.go +++ b/ssa/ssa_test.go @@ -557,3 +557,65 @@ _llgo_0: } `) } + +func TestGlobalStrings(t *testing.T) { + prog := NewProgram(nil) + prog.SetRuntime(func() *types.Package { + fset := token.NewFileSet() + imp := packages.NewImporter(fset) + pkg, _ := imp.Import(PkgRuntime) + return pkg + }) + pkg := prog.NewPackage("bar", "foo/bar") + typ := types.NewPointer(types.Typ[types.String]) + a := pkg.NewVar("foo/bar.a", typ, InGo) + if pkg.NewVar("foo/bar.a", typ, InGo) != a { + t.Fatal("NewVar(a) failed") + } + a.InitNil() + pkg.NewVarEx("foo/bar.a", prog.Type(typ, InGo)) + b := pkg.NewVar("foo/bar.b", typ, InGo) + b.InitNil() + c := pkg.NewVar("foo/bar.c", types.NewPointer(types.Typ[types.Int]), InGo) + c.Init(prog.Val(100)) + assertPkg(t, pkg, `; ModuleID = 'foo/bar' +source_filename = "foo/bar" + +%"github.com/goplus/llgo/runtime/internal/runtime.String" = type { ptr, i64 } + +@"foo/bar.a" = global %"github.com/goplus/llgo/runtime/internal/runtime.String" zeroinitializer, align 8 +@"foo/bar.b" = global %"github.com/goplus/llgo/runtime/internal/runtime.String" zeroinitializer, align 8 +@"foo/bar.c" = global i64 100, align 8 +`) + err := pkg.Undefined("foo/bar.a", "foo/bar.b") + if err != nil { + t.Fatal(err) + } + pkg.Undefined("foo.bar.d") + err = pkg.Undefined("foo/bar.c") + if err == nil { + t.Fatal("must err") + } + assertPkg(t, pkg, `; ModuleID = 'foo/bar' +source_filename = "foo/bar" + +%"github.com/goplus/llgo/runtime/internal/runtime.String" = type { ptr, i64 } + +@"foo/bar.c" = global i64 100, align 8 +@"foo/bar.a" = external global %"github.com/goplus/llgo/runtime/internal/runtime.String" +@"foo/bar.b" = external global %"github.com/goplus/llgo/runtime/internal/runtime.String" +`) + global := prog.NewPackage("", "global") + global.AddGlobalString("foo/bar.a", "1.0") + global.AddGlobalString("foo/bar.b", "info") + assertPkg(t, global, `; ModuleID = 'global' +source_filename = "global" + +%"github.com/goplus/llgo/runtime/internal/runtime.String" = type { ptr, i64 } + +@0 = private unnamed_addr constant [3 x i8] c"1.0", align 1 +@"foo/bar.a" = global %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @0, i64 3 }, align 8 +@1 = private unnamed_addr constant [4 x i8] c"info", align 1 +@"foo/bar.b" = global %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @1, i64 4 }, align 8 +`) +}