Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 84 additions & 13 deletions internal/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package build
import (
"bytes"
"debug/macho"
"errors"
"fmt"
"go/ast"
"go/constant"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down
13 changes: 9 additions & 4 deletions runtime/internal/lib/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 1 addition & 22 deletions ssa/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 53 additions & 0 deletions ssa/globals.go
Original file line number Diff line number Diff line change
@@ -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
}
21 changes: 21 additions & 0 deletions ssa/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

// -----------------------------------------------------------------------------

/*
Expand Down
62 changes: 62 additions & 0 deletions ssa/ssa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
`)
}
Loading