Skip to content
Closed
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
4 changes: 4 additions & 0 deletions cl/_testgo/tlsgls/expect.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
main 11 22 33 3 41 1 1 101 1
g1 14 25 36 6 0 2 1 102 2
g2 17 28 39 9 0 3 1 103 3
main 11 22 33 9 41 3 1 101 3
31 changes: 31 additions & 0 deletions cl/_testgo/tlsgls/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// LITTEST
package main

import "github.com/goplus/llgo/cl/_testgo/tlsgls/state"

func printValues(owner string) {
// CHECK-LABEL: define void @"{{.*}}tlsgls.printValues"
// CHECK: call { i64, i64, i64, i64 } @"{{.*}}tlsgls/state.Values"()
tls, gls, pointer, sequence := state.Values()
// CHECK: call { i64, i64 } @"{{.*}}tlsgls/state.PoisonValues"()
poison, attempts := state.PoisonValues()
// CHECK: call { i64, i64 } @"{{.*}}tlsgls/state.LateValues"()
late, lateAttempts := state.LateValues()
// CHECK: call i64 @"{{.*}}tlsgls/state.RecursiveValue"()
println(owner, tls, gls, pointer, sequence, poison, attempts, state.RecursiveValue(), late, lateAttempts)
}

func run(owner string, done chan bool) {
printValues(owner)
done <- true
}

func main() {
printValues("main")
done := make(chan bool)
go run("g1", done)
<-done
go run("g2", done)
<-done
printValues("main")
}
100 changes: 100 additions & 0 deletions cl/_testgo/tlsgls/state/state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package state

import "runtime"

var sequence int

func next(base int) int {
sequence++
return base + sequence
}

func nextPointer(base int) *int {
value := next(base)
return &value
}

//llgo:tls
var TLS = next(10)

//llgo:gls
var GLS = next(20)

//llgo:gls
var Pointer = nextPointer(30)

var poisonAttempts int

func poisonOtherThreads() int {
poisonAttempts++
if poisonAttempts > 1 {
panic("poison TLS initializer")
}
return 40 + poisonAttempts
}

//llgo:tls
var Poison = poisonOtherThreads()

type recursiveInitializer interface {
value() int
}

type recursiveValue struct{}

func (recursiveValue) value() int {
return Recursive + 1
}

var recursiveSource recursiveInitializer = recursiveValue{}

//llgo:gls
var Recursive = recursiveSource.value()

var zLateAttempts int

func nextLate() int {
zLateAttempts++
return 100 + zLateAttempts
}

// zLate sorts after the synthetic package init function in SSA member order.
//
//llgo:tls
var zLate = nextLate()

func Values() (tls, gls, pointer, count int) {
tls = TLS
gls = GLS
if Pointer == nil {
panic("nil GLS pointer")
}
runtime.GC()
pointer = *Pointer
count = sequence
return
}

func readPoison() int {
return Poison
}

func PoisonValues() (value, attempts int) {
func() {
defer func() {
_ = recover()
}()
value = readPoison()
}()
value = readPoison()
attempts = poisonAttempts
return
}

func RecursiveValue() int {
return Recursive
}

func LateValues() (value, attempts int) {
return zLate, zLateAttempts
}
52 changes: 51 additions & 1 deletion cl/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ type context struct {

staticGlobalInits map[*ssa.Global]llssa.Expr
staticInitStores map[*ssa.Store]none
localInitGroups map[string]*localInitGroup
localGlobals map[*ssa.Global]llssa.Global
}

func (p *context) rewriteValue(name string) (string, bool) {
Expand Down Expand Up @@ -389,7 +391,14 @@ func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) {
return
}
dbgInstrln("==> NewVar", name, typ)
g := pkg.NewVar(name, typ, llssa.Background(vtype))
local, hasDecl := p.prog.DeclInfo(llssa.FullName(gbl.Pkg.Pkg, gbl.Name()))
isLocal := hasDecl && local.Locality != llssa.LocalityNone
var g llssa.Global
if isLocal {
g = p.localGlobal(pkg, local, gbl, name, typ, vtype)
} else {
g = pkg.NewVar(name, typ, llssa.Background(vtype))
}
if p.tryEmbedGlobalInit(pkg, gbl, g, name) {
return
}
Expand All @@ -409,6 +418,21 @@ func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) {
}
}

func (p *context) localGlobal(pkg llssa.Package, local llssa.DeclInfo, gbl *ssa.Global, name string, typ types.Type, vtype int) llssa.Global {
if global, ok := p.localGlobals[gbl]; ok {
return global
}
global := pkg.NewThreadLocalVar(name, typ, llssa.Background(vtype))
if p.localGlobals == nil {
p.localGlobals = make(map[*ssa.Global]llssa.Global)
}
p.localGlobals[gbl] = global
if local.EnsureFunc != "" {
p.registerLocalInitializer(pkg, local, global, gbl)
}
return global
}

func makeClosureCtx(pkg *types.Package, vars []*ssa.FreeVar) *types.Var {
n := len(vars)
flds := make([]*types.Var, n)
Expand Down Expand Up @@ -835,6 +859,7 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, do
}

if doModInit {
p.initializeLocalGuards(b)
if p.state != pkgInPatch {
p.applyEmbedInits(b)
}
Expand Down Expand Up @@ -1930,6 +1955,12 @@ func newPackageEx(prog llssa.Program, ct *CallerTracking, patches Patches, rewri
pkg.Pkg = pkgTypes
patch.Alt.Pkg = pkgTypes
}
if err = ParsePkgSyntax(prog, pkgProg.Fset, pkgTypes, files); err != nil {
return nil, nil, err
}
if err = validateLocalInitializers(prog, pkgTypes); err != nil {
return nil, nil, err
}
if pkgPath == llssa.PkgRuntime {
prog.SetRuntime(pkgTypes)
}
Expand Down Expand Up @@ -1992,6 +2023,7 @@ func newPackageEx(prog llssa.Program, ct *CallerTracking, patches Patches, rewri
if !ctx.skipall {
processPkg(ctx, ret, pkg)
}
ctx.buildLocalInitializers(ret)
for len(ctx.inits) > 0 {
inits := ctx.inits
ctx.inits = nil
Expand Down Expand Up @@ -2030,6 +2062,24 @@ func processPkg(ctx *context, ret llssa.Package, pkg *ssa.Package) {
sort.Slice(members, func(i, j int) bool {
return members[i].name < members[j].name
})
// Package init can reference any package global, including globals whose
// names sort after "init". Register every local group before compiling
// function bodies so the initial thread can premark all local guards.
for _, m := range members {
global, ok := m.val.(*ssa.Global)
if !ok || isCgoFuncPtrVar(global.Name()) {
continue
}
local, ok := ctx.prog.DeclInfo(llssa.FullName(global.Pkg.Pkg, global.Name()))
if !ok || local.Locality == llssa.LocalityNone {
continue
}
typ := ctx.patchType(global.Type())
name, vtype, _ := ctx.varName(global.Pkg.Pkg, global)
if vtype != pyVar {
ctx.localGlobal(ret, local, global, name, typ, vtype)
}
}

for _, m := range members {
member := m.val
Expand Down
72 changes: 72 additions & 0 deletions cl/directive.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2026 The XGo Authors (xgo.dev). 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 cl

import (
"go/ast"
"go/token"
"strings"
)

type sourceDirective struct {
Name string
Args string
Raw string
Pos token.Pos
}

func parseSourceDirective(comment *ast.Comment) (sourceDirective, bool) {
if comment == nil {
return sourceDirective{}, false
}
raw := comment.Text
var namespace, body string
switch {
case strings.HasPrefix(raw, "//go:"):
namespace, body = "go:", raw[len("//go:"):]
case strings.HasPrefix(raw, "//llgo:"):
namespace, body = "llgo:", raw[len("//llgo:"):]
case strings.HasPrefix(raw, "// llgo:"):
namespace, body = "llgo:", raw[len("// llgo:"):]
case strings.HasPrefix(raw, "//export "):
return sourceDirective{Name: "export", Args: strings.TrimSpace(raw[len("//export "):]), Raw: raw, Pos: comment.Pos()}, true
default:
return sourceDirective{}, false
}
body = strings.TrimSpace(body)
if body == "" {
return sourceDirective{}, false
}
name, args := body, ""
if idx := strings.IndexAny(body, " \t"); idx >= 0 {
name, args = body[:idx], strings.TrimSpace(body[idx+1:])
}
return sourceDirective{Name: namespace + name, Args: args, Raw: raw, Pos: comment.Pos()}, true
}

func sourceDirectives(doc *ast.CommentGroup) []sourceDirective {
if doc == nil {
return nil
}
ret := make([]sourceDirective, 0, len(doc.List))
for _, comment := range doc.List {
if directive, ok := parseSourceDirective(comment); ok {
ret = append(ret, directive)
}
}
return ret
}
Loading
Loading